diff --git a/include/Comment.h b/include/Comment.h new file mode 100644 index 000000000..693340a07 --- /dev/null +++ b/include/Comment.h @@ -0,0 +1,18 @@ + +#ifndef COMMENT_H_ +#define COMMENT_H_ + +#include "Types.h" +#include "Module.h" + +#include + +#include + +struct Comment { + QString comment; + edb::address_t address; + std::optional module; +}; + +#endif diff --git a/include/IBreakpoint.h b/include/IBreakpoint.h index 6ee675c59..4ad076a69 100644 --- a/include/IBreakpoint.h +++ b/include/IBreakpoint.h @@ -8,9 +8,11 @@ #define IBREAKPOINT_H_20060720_ #include "Types.h" +#include "Module.h" #include #include +#include class QByteArray; @@ -45,6 +47,7 @@ class IBreakpoint { [[nodiscard]] virtual const uint8_t *originalBytes() const = 0; [[nodiscard]] virtual size_t size() const = 0; [[nodiscard]] virtual TypeId type() const = 0; + [[nodiscard]] virtual std::optional module() const = 0; public: virtual bool enable() = 0; diff --git a/include/IPlugin.h b/include/IPlugin.h index cae32a12c..83e6a37c8 100644 --- a/include/IPlugin.h +++ b/include/IPlugin.h @@ -13,6 +13,7 @@ class QMenu; class QAction; +class Module; class IPlugin { public: @@ -40,6 +41,9 @@ class IPlugin { // optional, overload this to add a page to the options dialog [[nodiscard]] virtual QWidget *optionsPage() { return nullptr; } + // optional, overload this to get notified when a library is loaded or unloaded + virtual void libraryEvent(const Module & /*module*/, bool /*loaded*/) {} + public: [[nodiscard]] virtual QVariantMap saveState() const { return {}; } virtual void restoreState(const QVariantMap &) {} diff --git a/include/ISymbolManager.h b/include/ISymbolManager.h index f0b19cfe2..6b840511f 100644 --- a/include/ISymbolManager.h +++ b/include/ISymbolManager.h @@ -7,8 +7,12 @@ #ifndef ISYMBOL_MANAGER_H_20110307_ #define ISYMBOL_MANAGER_H_20110307_ +#include "Module.h" #include "Types.h" + #include +#include + #include #include @@ -17,11 +21,19 @@ class Symbol; class ISymbolGenerator; class ISymbolManager { +public: + struct LabelEntry { + QString name; + edb::address_t address; + std::optional module; + }; + public: virtual ~ISymbolManager() = default; public: - [[nodiscard]] virtual QHash labels() const = 0; + [[nodiscard]] virtual QVector labelData() const = 0; + [[nodiscard]] virtual QMap labels() const = 0; [[nodiscard]] virtual QString findAddressName(edb::address_t address, bool prefixed = true) = 0; [[nodiscard]] virtual QStringList files() const = 0; [[nodiscard]] virtual std::optional find(const QString &name) const = 0; diff --git a/include/Types.h b/include/Types.h index ca42e5153..3cb13a1b6 100644 --- a/include/Types.h +++ b/include/Types.h @@ -8,6 +8,7 @@ #define TYPES_H_20071127_ #include "Value.h" + #include namespace edb { @@ -23,11 +24,5 @@ enum EventStatus { } -/* Comment Type */ -struct Comment { - edb::address_t address; - QString comment; -}; - #include "ArchTypes.h" #endif diff --git a/include/edb.h b/include/edb.h index 331172e5e..b03c781eb 100644 --- a/include/edb.h +++ b/include/edb.h @@ -9,12 +9,15 @@ #include "API.h" #include "IBinary.h" +#include "Module.h" #include "Status.h" #include "Types.h" + #include #include #include #include + #include #include @@ -53,6 +56,8 @@ namespace v2 { EDB_EXPORT std::optional get_expression_from_user(const QString &title, const QString &prompt); EDB_EXPORT std::optional eval_expression(const QString &expression); EDB_EXPORT QString format_bytes(const void *buffer, size_t count); +EDB_EXPORT std::optional module_for_address(edb::address_t address); +EDB_EXPORT bool compare_module_names(const QString &name1, const QString &name2); } diff --git a/plugins/Analyzer/Analyzer.cpp b/plugins/Analyzer/Analyzer.cpp index dc9955245..7f995e90a 100644 --- a/plugins/Analyzer/Analyzer.cpp +++ b/plugins/Analyzer/Analyzer.cpp @@ -114,7 +114,8 @@ void set_function_types(IAnalyzer::FunctionMap *results) { Function &function = it.value(); if (function.empty()) { - qDebug() << "HERE:" << it.key().toString(); + qDebug() << "Function at " << function.entryAddress().toHexString() << " is empty, skipping type classification"; + continue; } Q_ASSERT(!function.empty()); diff --git a/plugins/Analyzer/AnalyzerWidget.cpp b/plugins/Analyzer/AnalyzerWidget.cpp index e0562dc2d..60b26cbbb 100644 --- a/plugins/Analyzer/AnalyzerWidget.cpp +++ b/plugins/Analyzer/AnalyzerWidget.cpp @@ -133,14 +133,14 @@ void AnalyzerWidget::paintEvent(QPaintEvent * /*event*/) { const int64_t renderTime = timer.elapsed(); if (renderTime > 8) { - qDebug() << "AnalyzerWidget: Painting took longer than desired: " << renderTime << "ms"; + qDebug() << "[AnalyzerWidget]: Painting took longer than desired: " << renderTime << "ms"; } } /** * @brief Handles a click on the overview bar by jumping the disassembly view to the corresponding address. * - * @param event + * @param event The mouse press event that triggered this function. */ void AnalyzerWidget::mousePressEvent(QMouseEvent *event) { diff --git a/plugins/BinaryInfo/ELFXX.cpp b/plugins/BinaryInfo/ELFXX.cpp index 8726355d6..1bd87b208 100644 --- a/plugins/BinaryInfo/ELFXX.cpp +++ b/plugins/BinaryInfo/ELFXX.cpp @@ -106,7 +106,7 @@ ELFXX::ELFXX(const std::shared_ptr ®ion) for (uint16_t entry = 0; entry < header_.e_phnum; ++entry) { if (!process->readBytes(phdr_base + (phdr_size * entry), &phdr, sizeof(phdr_type))) { - qDebug() << "Failed to read program header"; + qDebug("Failed to read program header"); break; } diff --git a/plugins/BinaryInfo/symbols.cpp b/plugins/BinaryInfo/symbols.cpp index 12fcaedd0..ba49548bd 100644 --- a/plugins/BinaryInfo/symbols.cpp +++ b/plugins/BinaryInfo/symbols.cpp @@ -477,7 +477,7 @@ bool generate_symbols_internal(QFile &file, std::shared_ptr &debugFile, s return true; } - qDebug() << "unknown file type"; + qDebug("unknown file type"); } return false; diff --git a/plugins/Bookmarks/BookmarkWidget.cpp b/plugins/Bookmarks/BookmarkWidget.cpp index a315b62dc..6551d7b50 100644 --- a/plugins/Bookmarks/BookmarkWidget.cpp +++ b/plugins/Bookmarks/BookmarkWidget.cpp @@ -8,7 +8,10 @@ #include "BookmarksModel.h" #include "Expression.h" #include "IBreakpoint.h" +#include "IDebugger.h" +#include "IProcess.h" #include "edb.h" + #include #include #include @@ -175,6 +178,7 @@ void BookmarkWidget::addAddress(edb::address_t address, const QString &type, con address, BookmarksModel::bookmarkStringToType(type), comment, + edb::v2::module_for_address(address), }; model_->addBookmark(bookmark); @@ -277,11 +281,10 @@ void BookmarkWidget::on_tableView_customContextMenuRequested(const QPoint &pos) /** * @brief Returns a copy of the current bookmark list. * - * @return + * @return A QVector of BookmarksModel::Bookmark entries. */ -QList BookmarkWidget::entries() const { - const QVector &bookmarks = model_->bookmarks(); - return bookmarks.toList(); +QVector BookmarkWidget::entries() const { + return model_->bookmarks(); } // This is copied from Debugger::createAction, so really there should either be a class that implements diff --git a/plugins/Bookmarks/BookmarkWidget.h b/plugins/Bookmarks/BookmarkWidget.h index 49d4523e1..c6452ffa5 100644 --- a/plugins/Bookmarks/BookmarkWidget.h +++ b/plugins/Bookmarks/BookmarkWidget.h @@ -10,6 +10,7 @@ #include "BookmarksModel.h" #include "Types.h" #include "ui_BookmarkWidget.h" + #include class QModelIndex; @@ -32,7 +33,7 @@ public Q_SLOTS: public: void shortcut(int index); void addAddress(edb::address_t address, const QString &type = QString(), const QString &comment = QString()); - [[nodiscard]] QList entries() const; + [[nodiscard]] QVector entries() const; private: void buttonAddClicked(); diff --git a/plugins/Bookmarks/Bookmarks.cpp b/plugins/Bookmarks/Bookmarks.cpp index fb54e9385..20ef3c46a 100644 --- a/plugins/Bookmarks/Bookmarks.cpp +++ b/plugins/Bookmarks/Bookmarks.cpp @@ -6,7 +6,11 @@ #include "Bookmarks.h" #include "BookmarkWidget.h" +#include "IDebugger.h" +#include "IProcess.h" +#include "Module.h" #include "edb.h" + #include #include #include @@ -108,12 +112,14 @@ void Bookmarks::addBookmarkMenu() { QVariantMap Bookmarks::saveState() const { QVariantMap state; QVariantList bookmarks; + for (auto &bookmark : bookmarkWidget_->entries()) { QVariantMap entry; - entry[QStringLiteral("address")] = bookmark.address.toHexString(); entry[QStringLiteral("type")] = BookmarksModel::bookmarkTypeToString(bookmark.type); entry[QStringLiteral("comment")] = bookmark.comment; + entry[QStringLiteral("module")] = bookmark.module ? bookmark.module->name : QString(); + entry[QStringLiteral("offset")] = (bookmark.module) ? (bookmark.address - bookmark.module->baseAddress).toHexString() : QString(); bookmarks.push_back(entry); } @@ -129,17 +135,63 @@ QVariantMap Bookmarks::saveState() const { */ void Bookmarks::restoreState(const QVariantMap &state) { + IProcess *process = edb::v1::debugger_core->process(); + Q_ASSERT(process); + + QSet modules = process->loadedModules(); + QVariantList bookmarks = state[QStringLiteral("bookmarks")].toList(); for (auto &entry : bookmarks) { auto bookmark = entry.value(); - auto address = edb::address_t::fromHexString(bookmark[QStringLiteral("address")].toString()); - QString type = bookmark[QStringLiteral("type")].toString(); - QString comment = bookmark[QStringLiteral("comment")].toString(); + QString module_name = bookmark[QStringLiteral("module")].toString(); + QString offset_str = bookmark[QStringLiteral("offset")].toString(); + QString type = bookmark[QStringLiteral("type")].toString(); + QString comment = bookmark[QStringLiteral("comment")].toString(); + + edb::address_t offset = edb::address_t::fromHexString(offset_str); + + // Figure out which module this bookmark belongs to and add it if the module is loaded + auto it = std::find_if(modules.begin(), modules.end(), [&module_name](const Module &module) { + return edb::v2::compare_module_names(module.name, module_name); + }); + + if (it != modules.end()) { + edb::address_t address = offset + it->baseAddress; + bookmarkWidget_->addAddress(address, type, comment); + continue; + } else { + + // If the module is not loaded, store the bookmark entry for later restoration when the module is loaded + BookmarkEntry entry; + entry.type = type; + entry.comment = comment; + entry.module = module_name; + entry.offset = offset_str; + deferredBookmarks_.push_back(entry); + } + } +} - qDebug() << "Restoring bookmark with address: " << address.toHexString(); +/** + * @brief Handles library load/unload events to restore bookmarks for newly loaded modules. + * + * @param module The module that was loaded or unloaded. + * @param loaded True if the module was loaded, false if it was unloaded. + */ +void Bookmarks::libraryEvent(const Module &module, bool loaded) { + if (loaded) { + auto it = std::remove_if(deferredBookmarks_.begin(), deferredBookmarks_.end(), [&module, this](const BookmarkEntry &entry) { + if (edb::v2::compare_module_names(entry.module, module.name)) { + edb::address_t offset = edb::address_t::fromHexString(entry.offset); + edb::address_t address = offset + module.baseAddress; + bookmarkWidget_->addAddress(address, entry.type, entry.comment); + return true; + } - bookmarkWidget_->addAddress(address, type, comment); + return false; + }); + deferredBookmarks_.erase(it, deferredBookmarks_.end()); } } diff --git a/plugins/Bookmarks/Bookmarks.h b/plugins/Bookmarks/Bookmarks.h index b90b3ea3d..45fc9ef92 100644 --- a/plugins/Bookmarks/Bookmarks.h +++ b/plugins/Bookmarks/Bookmarks.h @@ -23,6 +23,14 @@ class Bookmarks : public QObject, public IPlugin { Q_CLASSINFO("author", "Evan Teran") Q_CLASSINFO("url", "http://www.codef00.com") +private: + struct BookmarkEntry { + QString type; + QString comment; + QString module; + QString offset; + }; + public: explicit Bookmarks(QObject *parent = nullptr); @@ -34,12 +42,18 @@ class Bookmarks : public QObject, public IPlugin { [[nodiscard]] QVariantMap saveState() const override; void restoreState(const QVariantMap &) override; +public: + void libraryEvent(const Module &module, bool loaded) override; + private: void addBookmarkMenu(); private: QMenu *menu_ = nullptr; BookmarkWidget *bookmarkWidget_ = nullptr; + + // These are the ones not restored yet, but will be restored when the modules are loaded + std::vector deferredBookmarks_; }; } diff --git a/plugins/Bookmarks/BookmarksModel.cpp b/plugins/Bookmarks/BookmarksModel.cpp index 9c53fb306..21eb49359 100644 --- a/plugins/Bookmarks/BookmarksModel.cpp +++ b/plugins/Bookmarks/BookmarksModel.cpp @@ -128,9 +128,9 @@ QVariant BookmarksModel::data(const QModelIndex &index, int role) const { * * @param r */ -void BookmarksModel::addBookmark(const Bookmark &r) { +void BookmarksModel::addBookmark(const Bookmark &bookmark) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); - bookmarks_.push_back(r); + bookmarks_.push_back(bookmark); endInsertRows(); } diff --git a/plugins/Bookmarks/BookmarksModel.h b/plugins/Bookmarks/BookmarksModel.h index 8c3d840bc..1d4dcb2a5 100644 --- a/plugins/Bookmarks/BookmarksModel.h +++ b/plugins/Bookmarks/BookmarksModel.h @@ -7,11 +7,15 @@ #ifndef BOOKMARKS_MODEL_H_20170103_ #define BOOKMARKS_MODEL_H_20170103_ +#include "Module.h" #include "Types.h" + #include #include #include +#include + namespace BookmarksPlugin { class BookmarksModel final : public QAbstractItemModel { @@ -28,6 +32,9 @@ class BookmarksModel final : public QAbstractItemModel { edb::address_t address; Type type; QString comment; + + // internal use only, used to store the module that the bookmark belongs to, if any + std::optional module; }; static QString bookmarkTypeToString(Bookmark::Type type) { @@ -72,7 +79,7 @@ class BookmarksModel final : public QAbstractItemModel { [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; public Q_SLOTS: - void addBookmark(const Bookmark &r); + void addBookmark(const Bookmark &bookmark); void clearBookmarks(); void deleteBookmark(const QModelIndex &index); void setComment(const QModelIndex &index, const QString &comment); diff --git a/plugins/CheckVersion/CheckVersion.cpp b/plugins/CheckVersion/CheckVersion.cpp index 1f0956f89..e21077f11 100644 --- a/plugins/CheckVersion/CheckVersion.cpp +++ b/plugins/CheckVersion/CheckVersion.cpp @@ -169,7 +169,7 @@ void CheckVersion::requestFinished(QNetworkReply *reply) { return; } - qDebug("comparing versions: [%d] [%d]", edb::v1::int_version(version), edb::v1::edb_version()); + qDebug("[CheckVersion] comparing versions: [%d] [%d]", edb::v1::int_version(version), edb::v1::edb_version()); if (edb::v1::int_version(version) > edb::v1::edb_version()) { QMessageBox msg; diff --git a/plugins/DebuggerCore/DebuggerCoreBase.cpp b/plugins/DebuggerCore/DebuggerCoreBase.cpp index eb0d8d455..9e532ca5d 100644 --- a/plugins/DebuggerCore/DebuggerCoreBase.cpp +++ b/plugins/DebuggerCore/DebuggerCoreBase.cpp @@ -42,7 +42,7 @@ std::shared_ptr DebuggerCoreBase::addBreakpoint(edb::address_t addr return nullptr; } catch (const BreakpointCreationError &) { - qDebug() << "Failed to create breakpoint"; + qDebug("Failed to create breakpoint"); return nullptr; } } diff --git a/plugins/DebuggerCore/arch/arm-generic/Breakpoint.cpp b/plugins/DebuggerCore/arch/arm-generic/Breakpoint.cpp index af62ce131..88db5f6d0 100644 --- a/plugins/DebuggerCore/arch/arm-generic/Breakpoint.cpp +++ b/plugins/DebuggerCore/arch/arm-generic/Breakpoint.cpp @@ -19,7 +19,7 @@ const std::vector BreakpointInstructionThumb_LE = {0x01, 0xde}; // correctly use it see GDB's thumb_get_next_pcs_raw function and comments // around arm_linux_thumb2_le_breakpoint array. const std::vector BreakpointInstructionThumb2_LE = {0xf0, 0xf7, 0x00, 0xa0}; // udf.w #0 -// This one generates SIGILL both in ARM32 and Thumb mode. In ARM23 mode it's decoded as UDF 0xDDE0, while +// This one generates SIGILL both in ARM32 and Thumb mode. In ARM32 mode it's decoded as UDF 0xDDE0, while // in Thumb it's a sequence `1: UDF 0xF0; B 1b`, which does stop the process even if it lands in the // middle (on the second half-word), although the signal still occurs at the first half-word. const std::vector BreakpointInstructionUniversalThumbARM_LE = {0xf0, 0xde, 0xfd, 0xe7}; @@ -38,6 +38,8 @@ Breakpoint::Breakpoint(edb::address_t address) if (!enable()) { throw BreakpointCreationError(); } + + module_ = edb::v2::module_for_address(address); } /** diff --git a/plugins/DebuggerCore/arch/arm-generic/Breakpoint.h b/plugins/DebuggerCore/arch/arm-generic/Breakpoint.h index 691a52b26..c6aadceae 100644 --- a/plugins/DebuggerCore/arch/arm-generic/Breakpoint.h +++ b/plugins/DebuggerCore/arch/arm-generic/Breakpoint.h @@ -43,6 +43,7 @@ class Breakpoint final : public IBreakpoint { [[nodiscard]] size_t size() const override { return originalBytes_.size(); } [[nodiscard]] const uint8_t *originalBytes() const override { return &originalBytes_[0]; } [[nodiscard]] IBreakpoint::TypeId type() const override { return type_; } + [[nodiscard]] std::optional module() const override { return module_; } [[nodiscard]] static std::vector supportedTypes(); [[nodiscard]] static std::vector possibleRewindSizes(); @@ -63,6 +64,7 @@ class Breakpoint final : public IBreakpoint { bool oneTime_ = false; bool internal_ = false; Type type_; + std::optional module_; }; } diff --git a/plugins/DebuggerCore/arch/x86-generic/Breakpoint.cpp b/plugins/DebuggerCore/arch/x86-generic/Breakpoint.cpp index accf6055a..3174bf668 100644 --- a/plugins/DebuggerCore/arch/x86-generic/Breakpoint.cpp +++ b/plugins/DebuggerCore/arch/x86-generic/Breakpoint.cpp @@ -38,6 +38,8 @@ Breakpoint::Breakpoint(edb::address_t address) if (!this->enable()) { throw BreakpointCreationError(); } + + module_ = edb::v2::module_for_address(address); } /** diff --git a/plugins/DebuggerCore/arch/x86-generic/Breakpoint.h b/plugins/DebuggerCore/arch/x86-generic/Breakpoint.h index 209ad83eb..a97d0a009 100644 --- a/plugins/DebuggerCore/arch/x86-generic/Breakpoint.h +++ b/plugins/DebuggerCore/arch/x86-generic/Breakpoint.h @@ -9,6 +9,7 @@ #include "IBreakpoint.h" #include "Util.h" + #include #include #include @@ -50,6 +51,7 @@ class Breakpoint final : public IBreakpoint { [[nodiscard]] size_t size() const override { return originalBytes_.size(); } [[nodiscard]] const uint8_t *originalBytes() const override { return originalBytes_.data(); } [[nodiscard]] IBreakpoint::TypeId type() const override { return type_; } + [[nodiscard]] std::optional module() const override { return module_; } [[nodiscard]] static std::vector supportedTypes(); [[nodiscard]] static std::vector possibleRewindSizes(); @@ -70,6 +72,7 @@ class Breakpoint final : public IBreakpoint { bool oneTime_ = false; bool internal_ = false; Type type_; + std::optional module_; }; } diff --git a/plugins/DebuggerCore/unix/linux/DebuggerCore.cpp b/plugins/DebuggerCore/unix/linux/DebuggerCore.cpp index b17a2570f..fe6b61e96 100644 --- a/plugins/DebuggerCore/unix/linux/DebuggerCore.cpp +++ b/plugins/DebuggerCore/unix/linux/DebuggerCore.cpp @@ -829,7 +829,7 @@ void DebuggerCore::detectCpuMode() { if (!errno) { if (cs == userCodeSegment32_) { if (pointerSize_ == sizeof(uint64_t)) { - qDebug() << "Debuggee is now 32 bit"; + qDebug("Debuggee is now 32 bit"); cpuMode_ = CpuMode::x86_32; CapstoneEDB::init(CapstoneEDB::Architecture::ARCH_X86); } @@ -839,7 +839,7 @@ void DebuggerCore::detectCpuMode() { if (cs == userCodeSegment64_) { if (pointerSize_ == sizeof(uint32_t)) { - qDebug() << "Debuggee is now 64 bit"; + qDebug("Debuggee is now 64 bit"); cpuMode_ = CpuMode::x86_64; CapstoneEDB::init(CapstoneEDB::Architecture::ARCH_AMD64); } diff --git a/plugins/DebuggerCore/unix/linux/PlatformProcess.cpp b/plugins/DebuggerCore/unix/linux/PlatformProcess.cpp index 6685a7327..f47a071f3 100644 --- a/plugins/DebuggerCore/unix/linux/PlatformProcess.cpp +++ b/plugins/DebuggerCore/unix/linux/PlatformProcess.cpp @@ -172,7 +172,8 @@ QSet get_loaded_modules(const IProcess *process) { if (map.l_addr) { Module module; - module.name = QString::fromLocal8Bit(path); + // NOTE(eteran): we use the path[0] check here because ld.so gives the "primary" module the name "" (empty string) + module.name = path[0] ? QString::fromLocal8Bit(path) : process->executable(); module.baseAddress = map.l_addr; ret.insert(std::move(module)); } @@ -187,7 +188,7 @@ QSet get_loaded_modules(const IProcess *process) { } } - // fallback + // fallback, unfortunately due to symlink shenanigans, this won't quite match the link_map results, but it's better than nothing if (ret.isEmpty()) { const QList> r = edb::v1::memory_regions().regions(); QSet found_modules; @@ -1006,7 +1007,7 @@ edb::address_t get_debug_pointer(const IProcess *process, edb::address_t phdr_me if (process->readBytes(phdr_memaddr + i * sizeof(elf_phdr), &phdr, sizeof(elf_phdr))) { if (phdr.p_type == PT_DYNAMIC) { if (phdr.p_memsz > 0x100000) { - qDebug() << "[get_debug_pointer] p_memsz is too large, skipping"; + qDebug("[get_debug_pointer] p_memsz is too large, skipping"); return 0; } diff --git a/plugins/HeapAnalyzer/DialogHeap.cpp b/plugins/HeapAnalyzer/DialogHeap.cpp index e6d10b028..02ec0189c 100644 --- a/plugins/HeapAnalyzer/DialogHeap.cpp +++ b/plugins/HeapAnalyzer/DialogHeap.cpp @@ -345,11 +345,11 @@ void DialogHeap::processPotentialPointers(const QHash targets; - qDebug() << "[Heap Analyzer] collecting possible targets addresses"; + qDebug("[Heap Analyzer] collecting possible targets addresses"); for (int row = 0; row < model_->rowCount(); ++row) { QModelIndex index = model_->index(row, 0); if (auto result = static_cast(index.internalPointer())) { @@ -362,7 +362,7 @@ void DialogHeap::detectPointers() { } } - qDebug() << "[Heap Analyzer] linking blocks to target addresses"; + qDebug("[Heap Analyzer] linking blocks to target addresses"); for (int row = 0; row < model_->rowCount(); ++row) { QModelIndex index = model_->index(row, 0); processPotentialPointers(targets, index); @@ -553,13 +553,13 @@ void DialogHeap::doFind() { if (heap_symbol_start != 0) { process->readBytes(heap_symbol_start, &start_address, edb::v1::pointer_size()); } else { - qDebug() << "[Heap Analyzer] __curbrk symbol not found in ld, falling back on heuristic! This may or may not work."; + qDebug("[Heap Analyzer] __curbrk symbol not found in ld, falling back on heuristic! This may or may not work."); } if (heap_symbol_end != 0) { process->readBytes(heap_symbol_end, &end_address, edb::v1::pointer_size()); } else { - qDebug() << "[Heap Analyzer] __curbrk symbol not found in libc, falling back on heuristic! This may or may not work."; + qDebug("[Heap Analyzer] __curbrk symbol not found in libc, falling back on heuristic! This may or may not work."); } if (start_address != 0 && end_address != 0) { @@ -575,7 +575,7 @@ void DialogHeap::doFind() { }); if (it != regions.end()) { - qDebug() << "Found a memory region named '[heap]', assuming that it provides sane bounds"; + qDebug("Found a memory region named '[heap]', assuming that it provides sane bounds"); if (start_address == 0) { start_address = (*it)->start(); diff --git a/plugins/InstructionInspector/Plugin.cpp b/plugins/InstructionInspector/Plugin.cpp index da4580d46..57aaac683 100644 --- a/plugins/InstructionInspector/Plugin.cpp +++ b/plugins/InstructionInspector/Plugin.cpp @@ -824,7 +824,7 @@ std::string runOBJDUMP(const std::vector &bytes, edb::address_t ad } const auto output = QString::fromUtf8(process.readAllStandardOutput()).split(QLatin1Char('\n')); - const auto addrStr = address.toHexString().toLower().replace(QRegularExpression(QStringLiteral("^0+")), QStringLiteral("")); + const auto addrStr = address.toHexString().toLower().replace(QRegularExpression(QStringLiteral("^0+")), QString()); QString result; for (auto &line : output) { @@ -975,7 +975,7 @@ std::pair normalizeOBJCONV(const QString &t const auto disasm = expectedMatch.captured(1).trimmed().replace(QRegularExpression(QStringLiteral(" +")), QStringLiteral(" ")); const auto result = addr + QStringLiteral(" ") + bytes + QStringLiteral(" ") + disasm; - bytes.replace(QRegularExpression(QStringLiteral("[^0-9a-fA-F]")), QStringLiteral("")); + bytes.replace(QRegularExpression(QStringLiteral("[^0-9a-fA-F]")), QString()); const std::size_t insnLength = bytes.length() / 2; return std::make_pair(result, insnLength); } @@ -1197,8 +1197,8 @@ std::string runOBJCONV(std::vector bytes, edb::address_t address) Instruction, } mode = LookingFor::FunctionBegin; - const QString addrFormatted = address.toHexString().toUpper().replace(QRegularExpression(QStringLiteral("^0+")), QStringLiteral("")); - const QString addrTruncatedFormatted = (address & UINT32_MAX).toHexString().toUpper().replace(QRegularExpression(QStringLiteral("^0+")), QStringLiteral("")); + const QString addrFormatted = address.toHexString().toUpper().replace(QRegularExpression(QStringLiteral("^0+")), QString()); + const QString addrTruncatedFormatted = (address & UINT32_MAX).toHexString().toUpper().replace(QRegularExpression(QStringLiteral("^0+")), QString()); for (const QByteArray &byteString : lines) { const auto line = QString::fromUtf8(byteString); diff --git a/plugins/ODbgRegisterView/arch/arm-generic/armGroups.cpp b/plugins/ODbgRegisterView/arch/arm-generic/armGroups.cpp index e524d1869..bc36a8791 100644 --- a/plugins/ODbgRegisterView/arch/arm-generic/armGroups.cpp +++ b/plugins/ODbgRegisterView/arch/arm-generic/armGroups.cpp @@ -53,8 +53,8 @@ const BitFieldDescription fpscrSTRDescription = { }, { tr("Set stride to 1"), - QStringLiteral(""), - QStringLiteral(""), + QString(), + QString(), tr("Set stride to 2"), }, }; diff --git a/plugins/ProcessProperties/DialogProcessProperties.cpp b/plugins/ProcessProperties/DialogProcessProperties.cpp index a0fbca534..2728875c0 100644 --- a/plugins/ProcessProperties/DialogProcessProperties.cpp +++ b/plugins/ProcessProperties/DialogProcessProperties.cpp @@ -494,7 +494,7 @@ void DialogProcessProperties::updateHandles() { const QFileInfoList entries = dir.entryInfoList(QStringList() << QStringLiteral("[0-9]*")); for (const QFileInfo &info : entries) { if (info.isSymLink()) { - QString symlink(info.symLinkTarget()); + QString symlink = info.symLinkTarget(); const QString type(file_type(symlink)); if (type == tr("Socket")) { diff --git a/src/Debugger.cpp b/src/Debugger.cpp index 9310a9411..0196f6b47 100644 --- a/src/Debugger.cpp +++ b/src/Debugger.cpp @@ -192,11 +192,11 @@ class RunUntilRet : public IDebugEventHandler { * then finally for the syscall bug. */ if (trap_reason == IDebugEvent::TRAP_BREAKPOINT) { - qDebug() << "Trap breakpoint"; + qDebug("Trap breakpoint"); // Take care of exit/terminated conditions; address == 0 may suffice to catch all, but not 100% sure. if (reason == IDebugEvent::EVENT_EXITED || reason == IDebugEvent::EVENT_TERMINATED || address == 0) { - qDebug() << "The process is no longer running."; + qDebug("The process is no longer running."); return pass_back_to_debugger(); } @@ -218,10 +218,10 @@ class RunUntilRet : public IDebugEventHandler { // If it wasn't internal, it was a user breakpoint. Pass back to Debugger. if (!bp->internal()) { - qDebug() << "Previous was not an internal breakpoint."; + qDebug("Previous was not an internal breakpoint."); return pass_back_to_debugger(); } - qDebug() << "Previous was an internal breakpoint."; + qDebug("Previous was an internal breakpoint."); bp->disable(); edb::v1::debugger_core->removeBreakpoint(bp->address()); } else { @@ -232,14 +232,14 @@ class RunUntilRet : public IDebugEventHandler { // If we are on our ret (or the instr after?), then ret. if (address == returnAddress_) { - qDebug() << QStringLiteral("On our terminator at 0x%1").arg(address, 0, 16); + qDebug() << "On our terminator at " << address.toHexString(); if (is_instruction_ret(address)) { - qDebug() << "Found ret; passing back to debugger"; + qDebug("Found ret; passing back to debugger"); return pass_back_to_debugger(); } // If not a ret, then step so we can find the next block terminator. - qDebug() << "Not ret. Single-stepping"; + qDebug("Not ret. Single-stepping"); return edb::DEBUG_CONTINUE_STEP; } @@ -259,14 +259,14 @@ class RunUntilRet : public IDebugEventHandler { qDebug() << QStringLiteral("Found terminator %1 at 0x%2").arg(QString::fromStdString(inst.mnemonic())).arg(address, 0, 16); // If we already had a breakpoint there, then just continue. if (std::shared_ptr bp = edb::v1::debugger_core->findBreakpoint(address)) { - qDebug() << QStringLiteral("Already a breakpoint at terminator 0x%1").arg(address, 0, 16); + qDebug() << "Already a breakpoint at terminator: " << address.toHexString(); return edb::DEBUG_CONTINUE; } // Otherwise, attempt to set a breakpoint there and continue. if (std::shared_ptr bp = edb::v1::debugger_core->addBreakpoint(address)) { ownBreakpoints_.emplace_back(address, bp); - qDebug() << QStringLiteral("Setting breakpoint at terminator 0x%1").arg(address, 0, 16); + qDebug() << "Setting breakpoint at terminator: " << address.toHexString(); bp->setInternal(true); bp->setOneTime(true); // If the 0xcc get's rm'd on next event, then // don't set it one time; we'll handle it manually @@ -297,7 +297,7 @@ class RunUntilRet : public IDebugEventHandler { return pass_back_to_debugger(); } - qDebug() << "The process is no longer running."; + qDebug("The process is no longer running."); return pass_back_to_debugger(); } @@ -637,10 +637,10 @@ QString Debugger::createTty() { const int rv = select(fd + 1, &set, nullptr, nullptr, &timeout); switch (rv) { case -1: - qDebug() << "An error occurred while attempting to get the TTY of the terminal sub-process"; + qDebug("An error occurred while attempting to get the TTY of the terminal sub-process"); break; case 0: - qDebug() << "A Timeout occurred while attempting to get the TTY of the terminal sub-process"; + qDebug("A Timeout occurred while attempting to get the TTY of the terminal sub-process"); break; default: if (read(fd, buf, sizeof(buf)) != -1) { @@ -1721,8 +1721,8 @@ void Debugger::on_actionApplication_Working_Directory_triggered() { */ void Debugger::mnuStackPush() { Register value(edb::v1::debuggeeIs32Bit() - ? make_Register(QStringLiteral(""), edb::value32(0), Register::TYPE_GPR) - : make_Register(QStringLiteral(""), edb::value64(0), Register::TYPE_GPR)); + ? make_Register(QString(), edb::value32(0), Register::TYPE_GPR) + : make_Register(QString(), edb::value64(0), Register::TYPE_GPR)); if (IProcess *process = edb::v1::debugger_core->process()) { if (std::shared_ptr thread = process->currentThread()) { @@ -2878,7 +2878,7 @@ QString Debugger::sessionFilename() const { QString session_path = edb::v1::config().session_path; if (session_path.isEmpty()) { if (show_path_notice) { - qDebug() << "No session path specified. Please set it in the preferences to enable sessions."; + qDebug("No session path specified. Please set it in the preferences to enable sessions."); show_path_notice = false; } return QString(); @@ -2973,10 +2973,8 @@ void Debugger::setInitialDebuggerState() { SessionManager &session_manager = SessionManager::instance(); - if (Result session_error = session_manager.loadSession(filename)) { - QVariantList comments_data = session_manager.comments(); - cpuView_->restoreComments(comments_data); - } else { + Result session_error = session_manager.loadSession(filename); + if (!session_error) { QMessageBox::warning( this, tr("Error Loading Session"), @@ -3605,7 +3603,7 @@ void Debugger::on_action_Reset_UI_triggered() { QSettings settings; settings.beginGroup(QStringLiteral("Window")); - settings.remove(QStringLiteral("")); + settings.remove(QString()); settings.endGroup(); ui_reset_ = true; } @@ -3633,6 +3631,7 @@ void Debugger::handle_library_event(IProcess *process, [[maybe_unused]] edb::add edb::linux_struct::r_debug dynamic_info; const bool ok = (process->readBytes(debug_pointer, &dynamic_info, sizeof(dynamic_info)) == sizeof(dynamic_info)); if (ok) { + switch (dynamic_info.r_state) { case edb::linux_struct::r_debug::RT_CONSISTENT: break; @@ -3642,13 +3641,25 @@ void Debugger::handle_library_event(IProcess *process, [[maybe_unused]] edb::add QSet modules = process->loadedModules(); QSet added_modules = modules - loadedModules_; - qDebug() << "Added modules:"; + qDebug("Added modules:"); for (const Module &module : added_modules) { qDebug() << " " << module.name << "@" << edb::v1::format_pointer(module.baseAddress); + + // Notify all plugins who care about library load/unload events + for (QObject *plugin : edb::v1::plugin_list()) { + if (auto p = qobject_cast(plugin)) { + p->libraryEvent(module, true); + } + } + + // Notify the session manager about the library load event + // NOTE(eteran): this is a bit "out of place", because we are using it to restore labels/comments, + // but it would be better if we informed the symbol manager more directly. But that's a larger refactor, + // so for now, this is fine. + SessionManager::instance().libraryEvent(module, true); } loadedModules_ = modules; - break; } case edb::linux_struct::r_debug::RT_DELETE: { @@ -3657,13 +3668,20 @@ void Debugger::handle_library_event(IProcess *process, [[maybe_unused]] edb::add QSet modules = process->loadedModules(); QSet removed_modules = loadedModules_ - modules; - qDebug() << "Removed modules:"; + qDebug("Removed modules:"); for (const Module &module : removed_modules) { qDebug() << " " << module.name << "@" << edb::v1::format_pointer(module.baseAddress); + + for (QObject *plugin : edb::v1::plugin_list()) { + if (auto p = qobject_cast(plugin)) { + p->libraryEvent(module, false); + } + } + + SessionManager::instance().libraryEvent(module, false); } loadedModules_ = modules; - break; } } diff --git a/src/ExpressionDialog.cpp b/src/ExpressionDialog.cpp index 6519a0220..67bef7d7b 100644 --- a/src/ExpressionDialog.cpp +++ b/src/ExpressionDialog.cpp @@ -57,8 +57,8 @@ ExpressionDialog::ExpressionDialog(const QString &title, const QString &prompt, } void ExpressionDialog::on_text_changed(const QString &text) { - QHash labels = edb::v1::symbol_manager().labels(); - edb::address_t resAddr = labels.key(text); + QMap labels = edb::v1::symbol_manager().labels(); + edb::address_t resAddr = labels.key(text); bool retval = false; diff --git a/src/SymbolManager.cpp b/src/SymbolManager.cpp index 4181274e4..9dd29d659 100644 --- a/src/SymbolManager.cpp +++ b/src/SymbolManager.cpp @@ -30,7 +30,7 @@ void SymbolManager::clear() { symbolsByFile_.clear(); symbolsByName_.clear(); labels_.clear(); - labelsByName_.clear(); + labelNames_.clear(); } /** @@ -45,7 +45,7 @@ void SymbolManager::loadSymbolFile(const QString &filename, edb::address_t base) if (symbol_directory.isEmpty()) { if (showPathNotice_) { - qDebug() << "No symbol path specified. Please set it in the preferences to enable symbols."; + qDebug("No symbol path specified. Please set it in the preferences to enable symbols."); showPathNotice_ = false; } return; @@ -312,21 +312,27 @@ void SymbolManager::setSymbolGenerator(ISymbolGenerator *generator) { */ void SymbolManager::setLabel(edb::address_t address, const QString &label) { if (label.isEmpty()) { - labelsByName_.remove(labels_[address]); + labelNames_.remove(labels_[address].name); labels_.remove(address); - } else { - - if (labelsByName_.contains(label) && labelsByName_[label] != address) { - QMessageBox::warning( - edb::v1::debugger_ui, - tr("Duplicate Label"), - tr("You are attempting to give two separate addresses the same label, this is not supported.")); - return; - } + return; + } - labels_[address] = label; - labelsByName_[label] = address; + if (labelNames_.contains(label) && labels_[address].name != label) { + QMessageBox::warning( + edb::v1::debugger_ui, + tr("Duplicate Label"), + tr("You are attempting to give two separate addresses the same label, this is not supported.")); + return; } + + LabelEntry newLabel = { + label, + address, + edb::v2::module_for_address(address), + }; + + labels_[address] = newLabel; + labelNames_.insert(label); } /** @@ -339,7 +345,7 @@ void SymbolManager::setLabel(edb::address_t address, const QString &label) { QString SymbolManager::findAddressName(edb::address_t address, bool prefixed) { auto it = labels_.find(address); if (it != labels_.end()) { - return it.value(); + return it.value().name; } if (const std::optional sym = find(address)) { @@ -354,8 +360,12 @@ QString SymbolManager::findAddressName(edb::address_t address, bool prefixed) { * * @return The labels for all addresses. */ -QHash SymbolManager::labels() const { - return labels_; +QMap SymbolManager::labels() const { + QMap result; + for (auto it = labels_.begin(); it != labels_.end(); ++it) { + result[it.key()] = it.value().name; + } + return result; } /** @@ -366,3 +376,16 @@ QHash SymbolManager::labels() const { QStringList SymbolManager::files() const { return symbolsByFile_.keys(); } + +/** + * @brief Gets the label data for all addresses. + * + * @return A QVector containing the label data for all addresses. + */ +QVector SymbolManager::labelData() const { + QVector result; + for (auto it = labels_.begin(); it != labels_.end(); ++it) { + result.push_back(it.value()); + } + return result; +} diff --git a/src/SymbolManager.h b/src/SymbolManager.h index baf71d658..681e40b72 100644 --- a/src/SymbolManager.h +++ b/src/SymbolManager.h @@ -8,12 +8,15 @@ #define SYMBOL_MANAGER_H_20060814_ #include "ISymbolManager.h" +#include "Module.h" #include #include #include #include +#include + class QString; class SymbolManager final : public ISymbolManager { @@ -23,7 +26,8 @@ class SymbolManager final : public ISymbolManager { SymbolManager() = default; public: - [[nodiscard]] QHash labels() const override; + [[nodiscard]] QVector labelData() const override; + [[nodiscard]] QMap labels() const override; [[nodiscard]] QString findAddressName(edb::address_t address, bool prefixed = true) override; [[nodiscard]] QStringList files() const override; [[nodiscard]] std::optional find(const QString &name) const override; @@ -46,8 +50,8 @@ class SymbolManager final : public ISymbolManager { QMap symbolsByAddress_; QHash> symbolsByFile_; QHash symbolsByName_; - QHash labels_; - QHash labelsByName_; + QMap labels_; + QSet labelNames_; ISymbolGenerator *symbolGenerator_ = nullptr; bool showPathNotice_ = true; }; diff --git a/src/arch/arm-generic/ArchProcessor.cpp b/src/arch/arm-generic/ArchProcessor.cpp index b38d8a7e3..93825ce57 100644 --- a/src/arch/arm-generic/ArchProcessor.cpp +++ b/src/arch/arm-generic/ArchProcessor.cpp @@ -380,7 +380,7 @@ QString fpscrComment(edb::reg_t fpscr) { case 8: return QStringLiteral("(LT)"); default: - return QStringLiteral(""); + return QString(); } } diff --git a/src/capstone-edb/include/Instruction.h b/src/capstone-edb/include/Instruction.h index 0048c9deb..11eedc7e9 100644 --- a/src/capstone-edb/include/Instruction.h +++ b/src/capstone-edb/include/Instruction.h @@ -67,7 +67,7 @@ class EDB_EXPORT Instruction { #elif defined(EDB_ARM32) || defined(EDB_ARM64) return insn_ ? insn_->detail->arm.op_count : 0; #else -#error "What to return here?" + return 0; #endif } [[nodiscard]] std::size_t byteSize() const { return insn_ ? insn_->size : 1; } diff --git a/src/edb.cpp b/src/edb.cpp index 375d75666..f13a6cca8 100644 --- a/src/edb.cpp +++ b/src/edb.cpp @@ -23,6 +23,7 @@ #include "IRegion.h" #include "IThread.h" #include "MemoryRegions.h" +#include "Module.h" #include "Prototype.h" #include "QHexView" #include "QtHelper.h" @@ -42,8 +43,10 @@ #include #include #include +#include #include +#include IDebugger *edb::v1::debugger_core = nullptr; QWidget *edb::v1::debugger_ui = nullptr; @@ -923,10 +926,6 @@ std::unique_ptr get_binary_info(const std::shared_ptr ®ion) } } -#if 0 - qDebug() << "Failed to find any binary parser for region" - << QString::number(region->start(), 16); -#endif return nullptr; } @@ -1019,7 +1018,7 @@ std::shared_ptr primary_data_region() { } } - qDebug() << "primary data region not found!"; + qDebug("primary data region not found!"); return nullptr; } @@ -1646,5 +1645,69 @@ QString format_bytes(const void *buffer, size_t count) { return bytes; } +/** + * @brief Finds the module whose base address is closest to the given address, if any. + * + * @param address The address to find the module for. + * @return An optional Module object if a module is found, or std::nullopt if not. + */ +std::optional module_for_address(edb::address_t address) { + + IProcess *process = edb::v1::debugger_core->process(); + Q_ASSERT(process); + + QSet modules = process->loadedModules(); + + std::optional best_module; + + // Find which module whose base address is closest to the bookmark address + for (const auto &module : modules) { + if (module.baseAddress <= address) { + if (!best_module || module.baseAddress > best_module->baseAddress) { + best_module = module; + } + } + } + + return best_module; } + +/** + * @brief Compares two module names for equality, taking into account symbolic links and canonical paths. + * + * @param name1 The first module name to compare. + * @param name2 The second module name to compare. + * @return true if the module names refer to the same file, false otherwise. + */ +bool compare_module_names(const QString &name1, const QString &name2) { + + // Convert both names into canonical paths to ensure consistent comparison + // and then resolve any symbolic links or shortcuts to their target paths. + + QString canonicalName1 = QFileInfo(name1).canonicalFilePath(); + QString canonicalName2 = QFileInfo(name2).canonicalFilePath(); + + if (QFileInfo(canonicalName1).isSymLink()) { + canonicalName1 = QFileInfo(canonicalName1).symLinkTarget(); + } + + if (QFileInfo(canonicalName2).isSymLink()) { + canonicalName2 = QFileInfo(canonicalName2).symLinkTarget(); + } + + QT_STATBUF statbuf1; + if (QT_STAT(canonicalName1.toUtf8().data(), &statbuf1) != 0) { + return false; + } + + QT_STATBUF statbuf2; + if (QT_STAT(canonicalName2.toUtf8().data(), &statbuf2) != 0) { + return false; + } + + return statbuf1.st_dev == statbuf2.st_dev && statbuf1.st_ino == statbuf2.st_ino; +} + +} + } diff --git a/src/graph/GraphWidget.cpp b/src/graph/GraphWidget.cpp index 0fc99e7b0..7347f8938 100644 --- a/src/graph/GraphWidget.cpp +++ b/src/graph/GraphWidget.cpp @@ -89,7 +89,7 @@ GraphWidget::GraphWidget(QWidget *parent) // Set default attributes for the future nodes setNodeAttribute(QStringLiteral("fixedsize"), QStringLiteral("false")); - setNodeAttribute(QStringLiteral("label"), QStringLiteral("")); + setNodeAttribute(QStringLiteral("label"), QString()); setNodeAttribute(QStringLiteral("regular"), QStringLiteral("true")); // Divide the wanted width by the DPI to get the value in points @@ -173,7 +173,7 @@ void GraphWidget::layout() { inLayout_ = true; - qDebug() << "Starting Layout Engine"; + qDebug("Starting Layout Engine"); gvFreeLayout(context_, graph_); gvLayout(context_, graph_, "dot"); @@ -194,7 +194,7 @@ void GraphWidget::layout() { } } - qDebug() << "Layout Complete"; + qDebug("Layout Complete"); // make the scene HUGE so it feels like you can just scroll forever scene()->setSceneRect(sceneRect().adjusted(-ScenePadding, -ScenePadding, +ScenePadding, +ScenePadding)); diff --git a/src/session/SessionManager.cpp b/src/session/SessionManager.cpp index 4869ed448..a4d4df649 100644 --- a/src/session/SessionManager.cpp +++ b/src/session/SessionManager.cpp @@ -5,12 +5,18 @@ */ #include "SessionManager.h" +#include "IDebugger.h" #include "IPlugin.h" +#include "IProcess.h" +#include "Module.h" +#include "QDisassemblyView.h" +#include "SymbolManager.h" #include "edb.h" #include #include #include +#include #include #include #include @@ -18,7 +24,7 @@ namespace { -constexpr int SessionFileVersion = 1; +constexpr int SessionFileVersion = 2; const auto SessionFileIdString = QStringLiteral("edb-session"); } @@ -79,12 +85,15 @@ Result SessionManager::loadSession(const QString &filename) return make_unexpected(session_error); } - QJsonObject object = doc.object(); - sessionData_ = object.toVariantMap(); + QJsonObject sessionData = doc.object(); - QString id = sessionData_[QStringLiteral("id")].toString(); - QString ts = sessionData_[QStringLiteral("timestamp")].toString(); - int version = sessionData_[QStringLiteral("version")].toInt(); + const auto id = sessionData[QStringLiteral("id")].toString(); + const auto ts = sessionData[QStringLiteral("timestamp")].toString(); + const auto version = sessionData[QStringLiteral("version")].toInt(); + const auto labels = sessionData[QStringLiteral("labels")].toArray(); + const auto comments = sessionData[QStringLiteral("comments")].toArray(); + const auto breakpoints = sessionData[QStringLiteral("breakpoints")].toArray(); + const auto plugin_data = sessionData[QStringLiteral("plugin-data")].toObject(); Q_UNUSED(ts) @@ -96,7 +105,10 @@ Result SessionManager::loadSession(const QString &filename) } qDebug("Loading session file"); - loadPluginData(); // First, load the plugin-data + loadPluginData(plugin_data); + loadLabels(labels); + loadComments(comments); + loadBreakpoints(breakpoints); return {}; } @@ -124,13 +136,16 @@ void SessionManager::saveSession(const QString &filename) { } } - sessionData_[QStringLiteral("version")] = SessionFileVersion; - sessionData_[QStringLiteral("id")] = SessionFileIdString; // just so we can sanity check things - sessionData_[QStringLiteral("timestamp")] = QDateTime::currentDateTimeUtc(); - sessionData_[QStringLiteral("plugin-data")] = plugin_data; + QJsonObject sessionData; + sessionData[QStringLiteral("version")] = SessionFileVersion; + sessionData[QStringLiteral("id")] = SessionFileIdString; // just so we can sanity check things + sessionData[QStringLiteral("timestamp")] = QDateTime::currentDateTimeUtc().toString(); + sessionData[QStringLiteral("plugin-data")] = QJsonObject::fromVariantMap(plugin_data); + sessionData[QStringLiteral("labels")] = saveLabels(); + sessionData[QStringLiteral("comments")] = saveComments(); + sessionData[QStringLiteral("breakpoints")] = saveBreakpoints(); - auto object = QJsonObject::fromVariantMap(sessionData_); - QJsonDocument doc(object); + QJsonDocument doc(sessionData); QByteArray json = doc.toJson(); QFile file(filename); @@ -142,18 +157,19 @@ void SessionManager::saveSession(const QString &filename) { /** * @brief Loads the plugin data from the session. + * + * @param plugin_data The QJsonObject containing the plugin data to load. */ -void SessionManager::loadPluginData() { +void SessionManager::loadPluginData(const QJsonObject &plugin_data) { qDebug("Loading plugin-data"); - QVariantMap plugin_data = sessionData_[QStringLiteral("plugin-data")].toMap(); for (auto it = plugin_data.begin(); it != plugin_data.end(); ++it) { for (QObject *plugin : edb::v1::plugin_list()) { if (auto p = qobject_cast(plugin)) { if (const QMetaObject *const meta = plugin->metaObject()) { auto name = QString::fromLocal8Bit(meta->className()); - QVariantMap data = it.value().toMap(); + QVariantMap data = it.value().toObject().toVariantMap(); if (name == it.key()) { p->restoreState(data); @@ -166,59 +182,294 @@ void SessionManager::loadPluginData() { } /** - * @brief Returns all comments in the session + * @brief Saves the labels for session persistence. * - * @return A list of all comments in the session. + * @return A QVariantList containing the labels. */ -QVariantList SessionManager::comments() const { - return sessionData_[QStringLiteral("comments")].toList(); +QJsonArray SessionManager::saveLabels() const { + QVector labels = edb::v1::symbol_manager().labelData(); + + QJsonArray label_data; + + for (const auto &label : labels) { + + edb::address_t address = label.module ? label.address - label.module->baseAddress : edb::address_t(); + QString name = label.name; + + QJsonObject entry; + entry[QStringLiteral("module")] = label.module ? label.module->name : QString(); + entry[QStringLiteral("offset")] = address.toHexString(); + entry[QStringLiteral("name")] = name; + label_data.push_back(entry); + } + + return label_data; } /** - * @brief Adds a comment to the session + * @brief Saves the comments for session persistence. * - * @param c The comment to add. + * @return A QVariantList containing the comments. */ -void SessionManager::addComment(const Comment &c) { +QJsonArray SessionManager::saveComments() const { - QVariantList comments_data = sessionData_[QStringLiteral("comments")].toList(); + auto cpuView = qobject_cast(edb::v1::disassembly_widget()); + if (!cpuView) { + qDebug("Failed to get disassembly widget"); + return {}; + } + QVector comments = cpuView->commentData(); - QVariantMap comment; - comment[QStringLiteral("address")] = c.address.toHexString(); - comment[QStringLiteral("comment")] = c.comment; + QJsonArray comment_data; - // Check if we already have an entry with the same address and overwrite it - auto it = std::find_if(comments_data.begin(), comments_data.end(), [&comment](QVariant entry) { - QVariantMap data = entry.toMap(); - return data[QStringLiteral("address")] == comment[QStringLiteral("address")]; - }); + for (const auto &comment : comments) { - if (it != comments_data.end()) { - *it = comment; - } else { - comments_data.push_back(comment); + edb::address_t address = comment.module ? comment.address - comment.module->baseAddress : edb::address_t(); + QString comment_text = comment.comment; + + QJsonObject entry; + entry[QStringLiteral("module")] = comment.module ? comment.module->name : QString(); + entry[QStringLiteral("offset")] = address.toHexString(); + entry[QStringLiteral("name")] = comment_text; + comment_data.push_back(entry); } - sessionData_[QStringLiteral("comments")] = comments_data; + return comment_data; +} + +/** + * @brief Loads the labels for session restoration. + * + * @param labels A QJsonArray containing the labels to load. + */ +void SessionManager::loadLabels(const QJsonArray &labels) { + + qDebug("Loading labels"); + + IProcess *process = edb::v1::debugger_core->process(); + Q_ASSERT(process); + + QSet modules = process->loadedModules(); + + for (const QJsonValue &entry : labels) { + auto label = entry.toObject(); + + QString module_name = label[QStringLiteral("module")].toString(); + QString offset_str = label[QStringLiteral("offset")].toString(); + QString name = label[QStringLiteral("name")].toString(); + + edb::address_t offset = edb::address_t::fromHexString(offset_str); + + // Figure out which module this bookmark belongs to and add it if the module is loaded + auto it = std::find_if(modules.begin(), modules.end(), [&module_name](const Module &module) { + return edb::v2::compare_module_names(module.name, module_name); + }); + + if (it != modules.end()) { + edb::address_t address = offset + it->baseAddress; + edb::v1::symbol_manager().setLabel(address, name); + continue; + } else { + + // If the module is not loaded, store the bookmark entry for later restoration when the module is loaded + deferredLabels_.push_back(LabelEntry{ + name, + module_name, + offset_str, + }); + } + } } /** - * @brief Removes a comment from the session_data + * @brief Loads the comments for session restoration. * - * @param address The address of the comment to remove. + * @param comments A QJsonArray containing the comments to load. */ -void SessionManager::removeComment(edb::address_t address) { - QString hexAddressString = address.toHexString(); - QVariantList comments_data = sessionData_[QStringLiteral("comments")].toList(); +void SessionManager::loadComments(const QJsonArray &comments) { + qDebug("Loading comments"); + + IProcess *process = edb::v1::debugger_core->process(); + Q_ASSERT(process); + + QSet modules = process->loadedModules(); + + auto cpuView = qobject_cast(edb::v1::disassembly_widget()); + if (!cpuView) { + qDebug("Failed to get disassembly widget"); + return; + } + + for (const QJsonValue &entry : comments) { + auto comment = entry.toObject(); - auto it = std::find_if(comments_data.begin(), comments_data.end(), [&hexAddressString](QVariant entry) { - QVariantMap data = entry.toMap(); - return data[QStringLiteral("address")] == hexAddressString; - }); + QString module_name = comment[QStringLiteral("module")].toString(); + QString offset_str = comment[QStringLiteral("offset")].toString(); + QString name = comment[QStringLiteral("name")].toString(); - if (it != comments_data.end()) { - comments_data.erase(it); + edb::address_t offset = edb::address_t::fromHexString(offset_str); + + // Figure out which module this bookmark belongs to and add it if the module is loaded + auto it = std::find_if(modules.begin(), modules.end(), [&module_name](const Module &module) { + return edb::v2::compare_module_names(module.name, module_name); + }); + + if (it != modules.end()) { + edb::address_t address = offset + it->baseAddress; + cpuView->addComment(address, name); + continue; + } else { + + // If the module is not loaded, store the bookmark entry for later restoration when the module is loaded + deferredComments_.push_back(Comment{ + name, + offset, + edb::v2::module_for_address(offset), + }); + } } +} + +/** + * @brief Handles library events for loading and unloading modules. + * + * @param module The module that was loaded or unloaded. + * @param loaded True if the module was loaded, false if it was unloaded. + */ +void SessionManager::libraryEvent(const Module &module, bool loaded) { + if (loaded) { + // Load labels for the module that was just loaded + { + auto it = std::remove_if(deferredLabels_.begin(), deferredLabels_.end(), [&module, this](const LabelEntry &entry) { + if (edb::v2::compare_module_names(entry.module, module.name)) { + edb::address_t offset = edb::address_t::fromHexString(entry.offset); + edb::address_t address = offset + module.baseAddress; + edb::v1::symbol_manager().setLabel(address, entry.name); + return true; + } - sessionData_[QStringLiteral("comments")] = comments_data; + return false; + }); + deferredLabels_.erase(it, deferredLabels_.end()); + } + + // Load comments for the module that was just loaded + { + auto cpuView = qobject_cast(edb::v1::disassembly_widget()); + if (!cpuView) { + qDebug("Failed to get disassembly widget"); + return; + } + + auto it = std::remove_if(deferredComments_.begin(), deferredComments_.end(), [&module, cpuView, this](const Comment &entry) { + if (entry.module && edb::v2::compare_module_names(entry.module->name, module.name)) { + edb::address_t address = entry.address + module.baseAddress; + cpuView->addComment(address, entry.comment); + return true; + } + + return false; + }); + deferredComments_.erase(it, deferredComments_.end()); + } + + // Load breakpoints for the module that was just loaded + { + auto it = std::remove_if(deferredBreakpoints_.begin(), deferredBreakpoints_.end(), [&module, this](const BreakpointEntry &entry) { + if (edb::v2::compare_module_names(entry.module, module.name)) { + edb::address_t offset = edb::address_t::fromHexString(entry.offset); + edb::address_t address = offset + module.baseAddress; + std::shared_ptr bp = edb::v1::debugger_core->addBreakpoint(address); + if (bp) { + bp->condition = entry.condition; + bp->setOneTime(entry.oneTime); + } + return true; + } + return false; + }); + deferredBreakpoints_.erase(it, deferredBreakpoints_.end()); + } + } +} + +/** + * @brief Saves the breakpoints for session persistence. + * + * @return A QVariantList containing the breakpoints. + */ +QJsonArray SessionManager::saveBreakpoints() const { + QJsonArray breakpoints; + + const IDebugger::BreakpointList breakpoint_state = edb::v1::debugger_core->backupBreakpoints(); + for (const std::shared_ptr &bp : breakpoint_state) { + if (bp->internal()) { + continue; + } + + // TODO(eteran): make relative to the module + + const edb::address_t address = bp->address(); + const QString condition = bp->condition; + const bool onetime = bp->oneTime(); + const edb::address_t offset = bp->module() ? address - bp->module()->baseAddress : edb::address_t(); + + QJsonObject entry; + entry[QStringLiteral("module")] = bp->module() ? bp->module()->name : QString(); + entry[QStringLiteral("offset")] = offset.toHexString(); + entry[QStringLiteral("condition")] = condition; + entry[QStringLiteral("one_time")] = onetime; + breakpoints.push_back(entry); + } + + return breakpoints; +} + +/** + * @brief Loads the breakpoints for session restoration. + * + * @param breakpoints A QJsonArray containing the breakpoints to load. + */ +void SessionManager::loadBreakpoints(const QJsonArray &breakpoints) { + qDebug("Loading breakpoints"); + + IProcess *process = edb::v1::debugger_core->process(); + Q_ASSERT(process); + + QSet modules = process->loadedModules(); + + for (const QJsonValue &entry : breakpoints) { + auto breakpoint = entry.toObject(); + + QString module_name = breakpoint[QStringLiteral("module")].toString(); + QString offset_str = breakpoint[QStringLiteral("offset")].toString(); + QString condition = breakpoint[QStringLiteral("condition")].toString(); + bool one_time = breakpoint[QStringLiteral("one_time")].toBool(); + + edb::address_t offset = edb::address_t::fromHexString(offset_str); + + // Figure out which module this bookmark belongs to and add it if the module is loaded + auto it = std::find_if(modules.begin(), modules.end(), [&module_name](const Module &module) { + return edb::v2::compare_module_names(module.name, module_name); + }); + + if (it != modules.end()) { + edb::address_t address = offset + it->baseAddress; + std::shared_ptr bp = edb::v1::debugger_core->addBreakpoint(address); + if (bp) { + bp->condition = condition; + bp->setOneTime(one_time); + } + continue; + } else { + + // If the module is not loaded, store the bookmark entry for later restoration when the module is loaded + deferredBreakpoints_.push_back(BreakpointEntry{ + module_name, + offset_str, + condition, + one_time, + }); + } + } } diff --git a/src/session/SessionManager.h b/src/session/SessionManager.h index 550eecf89..04bab6fac 100644 --- a/src/session/SessionManager.h +++ b/src/session/SessionManager.h @@ -7,17 +7,37 @@ #ifndef SESSION_MANAGER_H_20170928_ #define SESSION_MANAGER_H_20170928_ +#include "Comment.h" #include "SessionError.h" #include "Status.h" #include "Types.h" #include #include -#include + +#include + +class Module; +class QJsonArray; +class QJsonObject; class SessionManager { Q_DECLARE_TR_FUNCTIONS(SessionManager) +private: + struct LabelEntry { + QString name; + QString module; + QString offset; + }; + + struct BreakpointEntry { + QString module; + QString offset; + QString condition; + bool oneTime = false; + }; + private: SessionManager() = default; @@ -31,15 +51,23 @@ class SessionManager { public: Result loadSession(const QString &filename); void saveSession(const QString &filename); - [[nodiscard]] QVariantList comments() const; - void addComment(const Comment &c); - void removeComment(edb::address_t address); + +public: + void libraryEvent(const Module &module, bool loaded); private: - void loadPluginData(); + QJsonArray saveBreakpoints() const; + QJsonArray saveComments() const; + QJsonArray saveLabels() const; + void loadBreakpoints(const QJsonArray &breakpoints); + void loadComments(const QJsonArray &comments); + void loadLabels(const QJsonArray &labels); + void loadPluginData(const QJsonObject &plugin_data); private: - QVariantMap sessionData_; + std::vector deferredLabels_; + std::vector deferredComments_; + std::vector deferredBreakpoints_; }; #endif diff --git a/src/widgets/NavigationHistory.cpp b/src/widgets/NavigationHistory.cpp index 4a1104403..5e1086021 100644 --- a/src/widgets/NavigationHistory.cpp +++ b/src/widgets/NavigationHistory.cpp @@ -6,10 +6,20 @@ #include "NavigationHistory.h" +/** + * @brief Constructs a NavigationHistory object with the specified maximum count. + * + * @param count The maximum number of addresses to store in the navigation history. + */ NavigationHistory::NavigationHistory(int count) : maxCount_(count) { } +/** + * @brief Adds an address to the navigation history. + * + * @param address The address to add to the history. + */ void NavigationHistory::add(edb::address_t address) { if (list_.size() == maxCount_) { list_.removeFirst(); @@ -37,9 +47,14 @@ void NavigationHistory::add(edb::address_t address) { lastOp_ = LastOp::None; } -edb::address_t NavigationHistory::getNext() { +/** + * @brief Retrieves the next address in the navigation history. + * + * @return The next address in the history, or std::nullopt if there is no next address. + */ +std::optional NavigationHistory::getNext() { if (list_.isEmpty()) { - return edb::address_t(0); + return std::nullopt; } if (pos_ != (list_.size() - 1)) { @@ -50,9 +65,14 @@ edb::address_t NavigationHistory::getNext() { return list_.at(pos_); } -edb::address_t NavigationHistory::getPrev() { +/** + * @brief Retrieves the previous address in the navigation history. + * + * @return The previous address in the history, or std::nullopt if there is no previous address. + */ +std::optional NavigationHistory::getPrev() { if (list_.isEmpty()) { - return edb::address_t(0); + return std::nullopt; } if (pos_ != 0) { diff --git a/src/widgets/NavigationHistory.h b/src/widgets/NavigationHistory.h index 11ced1886..64fd005f0 100644 --- a/src/widgets/NavigationHistory.h +++ b/src/widgets/NavigationHistory.h @@ -11,6 +11,8 @@ #include +#include + class NavigationHistory { enum class LastOp { None = 0, @@ -21,8 +23,8 @@ class NavigationHistory { public: explicit NavigationHistory(int count = 100); void add(edb::address_t address); - [[nodiscard]] edb::address_t getNext(); - [[nodiscard]] edb::address_t getPrev(); + [[nodiscard]] std::optional getNext(); + [[nodiscard]] std::optional getPrev(); private: QList list_; diff --git a/src/widgets/QDisassemblyView.cpp b/src/widgets/QDisassemblyView.cpp index 377c4405a..c4aa398df 100644 --- a/src/widgets/QDisassemblyView.cpp +++ b/src/widgets/QDisassemblyView.cpp @@ -16,7 +16,6 @@ #include "IThread.h" #include "Instruction.h" #include "MemoryRegions.h" -#include "SessionManager.h" #include "State.h" #include "SyntaxHighlighter.h" #include "Theme.h" @@ -267,14 +266,14 @@ void QDisassemblyView::keyPressEvent(QKeyEvent *event) { setSelectedAddress(showAddresses_[selectedLine]); } } else if (event->key() == Qt::Key_Minus) { - edb::address_t prev_addr = history_.getPrev(); - if (prev_addr != 0) { - edb::v1::jump_to_address(prev_addr); + std::optional prev_addr = history_.getPrev(); + if (prev_addr) { + edb::v1::jump_to_address(*prev_addr); } } else if (event->key() == Qt::Key_Plus) { - edb::address_t next_addr = history_.getNext(); - if (next_addr != 0) { - edb::v1::jump_to_address(next_addr); + std::optional next_addr = history_.getNext(); + if (next_addr) { + edb::v1::jump_to_address(*next_addr); } } else if (event->key() == Qt::Key_Down && (event->modifiers() & Qt::ControlModifier)) { const int address = verticalScrollBar()->value(); @@ -779,7 +778,7 @@ int QDisassemblyView::updateDisassembly(int lines_to_render) { const edb::address_t start_address = addressOffset_ + verticalScrollBar()->value(); if (!edb::v1::get_instruction_bytes(start_address, inst_buf, &bufsize)) { - qDebug() << "Failed to read" << bufsize << "bytes from" << QString::number(start_address, 16); + qDebug() << "[QDisassemblyView] Failed to read" << bufsize << "bytes from" << start_address.toHexString(); lines_to_render = 0; } @@ -807,6 +806,7 @@ int QDisassemblyView::updateDisassembly(int lines_to_render) { } line++; } + Q_ASSERT(line <= lines_to_render); if (lines_to_render != line) { partialLastLine_ = false; @@ -1228,8 +1228,10 @@ void QDisassemblyView::drawComments(QPainter &painter, const DrawingContext *ctx painter.setPen(palette().color(ctx->group, QPalette::Text)); } - QString annotation = comments_.value(address, QStringLiteral("")); - auto &&inst = instructions_[line]; + const Comment &comment_entry = comments_.value(address, Comment()); + QString annotation = comment_entry.comment; + + auto &&inst = instructions_[line]; if (annotation.isEmpty() && inst && !is_jump(inst) && !is_call(inst)) { // draw ascii representations of immediate constants size_t op_count = inst.operandCount(); @@ -1684,7 +1686,7 @@ void QDisassemblyView::paintEvent(QPaintEvent * /*event*/) { const int64_t renderTime = timer.elapsed(); if (renderTime > 50) { - qDebug() << "Painting took longer than desired: " << renderTime << "ms"; + qDebug() << "[QDisassemblyView] Painting took longer than desired: " << renderTime << "ms"; } } @@ -2184,27 +2186,27 @@ std::shared_ptr QDisassemblyView::region() const { } /** - * @brief Adds a comment to the comment hash and persists it in the session manager. + * @brief Adds a comment to the comment hash. * * @param address The address to associate the comment with. * @param comment The comment text to add. */ void QDisassemblyView::addComment(edb::address_t address, QString comment) { Comment temp_comment = { + comment, address, - comment}; - SessionManager::instance().addComment(temp_comment); - comments_.insert(address, comment); + edb::v2::module_for_address(address), + }; + comments_.insert(address, temp_comment); } /** - * @brief Removes a comment associated with the given address from the comment hash and the session manager. + * @brief Removes a comment associated with the given address from the comment hash. * * @param address The address of the comment to remove. * @return The number of comments removed (0 if no comment was found for the address, 1 if a comment was successfully removed). */ int QDisassemblyView::removeComment(edb::address_t address) { - SessionManager::instance().removeComment(address); return comments_.remove(address); } @@ -2215,7 +2217,9 @@ int QDisassemblyView::removeComment(edb::address_t address) { * @return The comment string associated with the address, or an empty string if no comment exists for that address. */ QString QDisassemblyView::getComment(edb::address_t address) const { - return comments_.value(address, QStringLiteral("")); + + const Comment &comment_entry = comments_.value(address, Comment()); + return comment_entry.comment; } /** @@ -2275,16 +2279,20 @@ void QDisassemblyView::restoreState(const QByteArray &stateBuffer) { line4_ = state.line4; } } + /** - * @brief Restores comments from a QVariantList containing comment data, inserting them into the comment hash based on their associated addresses. + * @brief Retrieves all comments stored in the disassembly view. * - * @param comments_data A QVariantList containing comment data, where each entry is a QVariantMap with "address" and "comment" keys. + * @return All comments, where each comment is represented as a Comment object with its associated address and text. */ -void QDisassemblyView::restoreComments(QVariantList &comments_data) { - for (const QVariant &entry : comments_data) { - QVariantMap data = entry.toMap(); - if (const Result addr = edb::v1::string_to_address(data[QStringLiteral("address")].toString())) { - comments_.insert(*addr, data[QStringLiteral("comment")].toString()); - } +QVector QDisassemblyView::commentData() const { + + QVector comment_vector; + comment_vector.reserve(comments_.size()); + + for (const auto &comment_entry : comments_) { + comment_vector.push_back(comment_entry); } + + return comment_vector; } diff --git a/src/widgets/QDisassemblyView.h b/src/widgets/QDisassemblyView.h index 9423df1e9..d8c3bbfcb 100644 --- a/src/widgets/QDisassemblyView.h +++ b/src/widgets/QDisassemblyView.h @@ -7,6 +7,7 @@ #ifndef QDISASSEMBLY_VIEW_H_20061101_ #define QDISASSEMBLY_VIEW_H_20061101_ +#include "Comment.h" #include "NavigationHistory.h" #include "Types.h" @@ -69,10 +70,13 @@ class QDisassemblyView final : public QAbstractScrollArea { [[nodiscard]] QByteArray saveState() const; [[nodiscard]] QString getComment(edb::address_t address) const; [[nodiscard]] std::shared_ptr region() const; + int removeComment(edb::address_t address); void addComment(edb::address_t address, QString comment); void clearComments(); - void restoreComments(QVariantList &); + + [[nodiscard]] QVector commentData() const; + void restoreState(const QByteArray &stateBuffer); void setSelectedAddress(edb::address_t address); @@ -162,7 +166,7 @@ public Q_SLOTS: std::vector instructions_; SyntaxHighlighter *highlighter_; bool showAddressSeparator_; - QHash comments_; + QHash comments_; NavigationHistory history_; QSvgRenderer breakpointRenderer_; QSvgRenderer currentRenderer_;