From 29bd3d0dbd1359a9cd9c75eff20716054002e2b6 Mon Sep 17 00:00:00 2001 From: Giovanni <561184+wargio@users.noreply.github.com> Date: Sat, 25 Jun 2022 15:03:45 +0200 Subject: [PATCH 01/77] Add back jsdec which was wrongly removed. (#2983) --- .github/workflows/ccpp.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index a1c8e0c5..4adc9fd1 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -123,6 +123,7 @@ jobs: -DCUTTER_ENABLE_SIGDB=ON \ -DCUTTER_ENABLE_DEPENDENCY_DOWNLOADS=ON \ -DCUTTER_PACKAGE_RZ_GHIDRA=ON \ + -DCUTTER_PACKAGE_JSDEC=ON \ -DCUTTER_PACKAGE_RZ_LIBSWIFT=ON \ -DCUTTER_PACKAGE_RZ_LIBYARA=ON \ -DCMAKE_INSTALL_PREFIX=appdir/usr \ From ddacbd78280a62b48fbacfce382cc09b0f86d8ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 25 Jun 2022 15:20:50 +0200 Subject: [PATCH 02/77] Extend blocksize to work around pdJ printing to few lines (#2984) --- src/core/Cutter.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index abb86e4b..76397be8 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -219,6 +219,11 @@ void CutterCore::initialize(bool loadPlugins) // Otherwise Rizin may ask the user for input and Cutter would freeze setConfig("scr.interactive", false); + // Temporary workaround for https://github.com/rizinorg/rizin/issues/2741 + // Otherwise sometimes disassembly is truncated. + // The blocksize here is a rather arbitrary value larger than the default 0x100. + rz_core_block_size(core, 0x400); + // Initialize graph node highlighter bbHighlighter = new BasicBlockHighlighter(); From df71da8f535b82992d2166ac918072d0ac97ed80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 1 Jul 2022 16:49:58 +0200 Subject: [PATCH 03/77] Update rizin dev (#2987) Relevant breaking commits in rizin: 9ea7c2fa5acd0b9b0c178a1b4316adc129e7c512 2987e035da827903329ec6433410899b985e2434 --- cmake/BundledRizin.cmake | 2 +- rizin | 2 +- src/core/Cutter.cpp | 2 +- src/widgets/HexdumpWidget.cpp | 12 ++++++------ 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmake/BundledRizin.cmake b/cmake/BundledRizin.cmake index 7955c279..0a3c4491 100644 --- a/cmake/BundledRizin.cmake +++ b/cmake/BundledRizin.cmake @@ -56,7 +56,7 @@ endif() # TODO: This version number should be fetched automatically # instead of being hardcoded. -set (Rizin_VERSION 0.4) +set (Rizin_VERSION 0.5) set (RZ_LIBS rz_core rz_config rz_cons rz_io rz_util rz_flag rz_asm rz_debug rz_hash rz_bin rz_lang rz_il rz_analysis rz_parse rz_bp rz_egg rz_reg diff --git a/rizin b/rizin index 6498ee67..0fc9c968 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit 6498ee6760d37f6abb11784aa550aecd2e03100b +Subproject commit 0fc9c9682e8a74245e4f24b84508f2bb6b185328 diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 76397be8..61882cfc 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -4232,7 +4232,7 @@ QString CutterCore::getVersionInformation() { "rz_crypto", &rz_crypto_version }, { "rz_bp", &rz_bp_version }, { "rz_debug", &rz_debug_version }, - { "rz_msg_digest", &rz_msg_digest_version }, + { "rz_hash", &rz_hash_version }, { "rz_io", &rz_io_version }, #if !USE_LIB_MAGIC { "rz_magic", &rz_magic_version }, diff --git a/src/widgets/HexdumpWidget.cpp b/src/widgets/HexdumpWidget.cpp index ce12be37..e6a088c4 100644 --- a/src/widgets/HexdumpWidget.cpp +++ b/src/widgets/HexdumpWidget.cpp @@ -244,24 +244,24 @@ void HexdumpWidget::updateParseWindow(RVA start_address, int size) : ""); } else { // Fill the information tab hashes and entropy - RzMsgDigestSize digest_size = 0; + RzHashSize digest_size = 0; RzCoreLocked core(Core()); ut64 old_offset = core->offset; rz_core_seek(core, start_address, true); ut8 *block = core->block; - char *digest = rz_msg_digest_calculate_small_block_string("md5", block, size, &digest_size, false); + char *digest = rz_hash_cfg_calculate_small_block_string(core->hash, "md5", block, size, &digest_size, false); ui->bytesMD5->setText(QString(digest)); free(digest); - digest = rz_msg_digest_calculate_small_block_string("sha1", block, size, &digest_size, false); + digest = rz_hash_cfg_calculate_small_block_string(core->hash, "sha1", block, size, &digest_size, false); ui->bytesSHA1->setText(QString(digest)); free(digest); - digest = rz_msg_digest_calculate_small_block_string("sha256", block, size, &digest_size, false); + digest = rz_hash_cfg_calculate_small_block_string(core->hash, "sha256", block, size, &digest_size, false); ui->bytesSHA256->setText(QString(digest)); free(digest); - digest = rz_msg_digest_calculate_small_block_string("crc32", block, size, &digest_size, false); + digest = rz_hash_cfg_calculate_small_block_string(core->hash, "crc32", block, size, &digest_size, false); ui->bytesCRC32->setText(QString(digest)); free(digest); - digest = rz_msg_digest_calculate_small_block_string("entropy", block, size, &digest_size, false); + digest = rz_hash_cfg_calculate_small_block_string(core->hash, "entropy", block, size, &digest_size, false); ui->bytesEntropy->setText(QString(digest)); free(digest); rz_core_seek(core, old_offset, true); From 91fce8220bcd8e643d119126c95a0d31c2154763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 2 Jul 2022 14:23:13 +0200 Subject: [PATCH 04/77] Remove direct download from update check (#2989) Hardcoded prediction of filenames for future releases is too prone to break, which is what happened with v2.1.0. So better to provide the link to the release page only. --- src/common/UpdateWorker.cpp | 124 ++---------------------------------- src/common/UpdateWorker.h | 64 ++----------------- 2 files changed, 9 insertions(+), 179 deletions(-) diff --git a/src/common/UpdateWorker.cpp b/src/common/UpdateWorker.cpp index 1514bc38..065e19c2 100644 --- a/src/common/UpdateWorker.cpp +++ b/src/common/UpdateWorker.cpp @@ -52,29 +52,6 @@ void UpdateWorker::checkCurrentVersion(time_t timeoutMs) pending = true; } -void UpdateWorker::download(QString filename, QString version) -{ - downloadFile.setFileName(filename); - downloadFile.open(QIODevice::WriteOnly); - - QNetworkRequest request; -# if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) && QT_VERSION < QT_VERSION_CHECK(5, 9, 0) - request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); -# elif QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) - request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, - QNetworkRequest::RedirectPolicy::NoLessSafeRedirectPolicy); -# endif - QUrl url(QString("https://github.com/rizinorg/cutter/releases/" - "download/v%1/%2") - .arg(version) - .arg(getRepositoryFileName())); - request.setUrl(url); - - downloadReply = nm.get(request); - connect(downloadReply, &QNetworkReply::downloadProgress, this, &UpdateWorker::process); - connect(downloadReply, &QNetworkReply::finished, this, &UpdateWorker::serveDownloadFinish); -} - void UpdateWorker::showUpdateDialog(bool showDontCheckForUpdatesButton) { QMessageBox mb; @@ -82,69 +59,23 @@ void UpdateWorker::showUpdateDialog(bool showDontCheckForUpdatesButton) mb.setText(tr("There is an update available for Cutter.
") + "" + tr("Current version:") + " " CUTTER_VERSION_FULL "
" + "" + tr("Latest version:") + " " + latestVersion.toString() + "

" - + tr("For update, please check the link:
") + + tr("To update, please check the link:
") + QString("" "https://github.com/rizinorg/cutter/releases/tag/v%1
") - .arg(latestVersion.toString()) - + tr("or click \"Download\" to download latest version of Cutter.")); + .arg(latestVersion.toString())); if (showDontCheckForUpdatesButton) { - mb.setStandardButtons(QMessageBox::Save | QMessageBox::Reset | QMessageBox::Ok); - mb.button(QMessageBox::Reset)->setText(tr("Don't check for updates")); + mb.setStandardButtons(QMessageBox::Reset | QMessageBox::Ok); + mb.button(QMessageBox::Reset)->setText(tr("Don't check for updates automatically")); } else { - mb.setStandardButtons(QMessageBox::Save | QMessageBox::Ok); + mb.setStandardButtons(QMessageBox::Ok); } - mb.button(QMessageBox::Save)->setText(tr("Download")); mb.setDefaultButton(QMessageBox::Ok); int ret = mb.exec(); if (ret == QMessageBox::Reset) { Config()->setAutoUpdateEnabled(false); - } else if (ret == QMessageBox::Save) { - QString fullFileName = QFileDialog::getSaveFileName( - nullptr, tr("Choose directory for downloading"), - QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() - + getRepositoryFileName(), - QString("%1 (*.%1)").arg(getRepositeryExt())); - if (!fullFileName.isEmpty()) { - QProgressDialog progressDial(tr("Downloading update..."), tr("Cancel"), 0, 100); - connect(this, &UpdateWorker::downloadProcess, &progressDial, - [&progressDial](size_t curr, size_t total) { - progressDial.setValue(100.0f * curr / total); - }); - connect(&progressDial, &QProgressDialog::canceled, this, &UpdateWorker::abortDownload); - connect(this, &UpdateWorker::downloadFinished, &progressDial, &QProgressDialog::cancel); - connect(this, &UpdateWorker::downloadFinished, this, [](QString filePath) { - QMessageBox info(QMessageBox::Information, tr("Download finished!"), - tr("Latest version of Cutter was succesfully downloaded!"), - QMessageBox::Yes | QMessageBox::Open | QMessageBox::Ok, nullptr); - info.button(QMessageBox::Open)->setText(tr("Open file")); - info.button(QMessageBox::Yes)->setText(tr("Open download folder")); - int r = info.exec(); - if (r == QMessageBox::Open) { - QDesktopServices::openUrl(filePath); - } else if (r == QMessageBox::Yes) { - auto path = filePath.split('/'); - path.removeLast(); - QDesktopServices::openUrl(path.join('/')); - } - }); - download(fullFileName, latestVersion.toString()); - // Calling show() before exec() is only way make dialog non-modal - // it seems weird, but it works - progressDial.show(); - progressDial.exec(); - } } } -void UpdateWorker::abortDownload() -{ - disconnect(downloadReply, &QNetworkReply::finished, this, &UpdateWorker::serveDownloadFinish); - disconnect(downloadReply, &QNetworkReply::downloadProgress, this, &UpdateWorker::process); - downloadReply->close(); - downloadReply->deleteLater(); - downloadFile.remove(); -} - void UpdateWorker::serveVersionCheckReply() { pending = false; @@ -168,51 +99,6 @@ void UpdateWorker::serveVersionCheckReply() emit checkComplete(versionReply, errStr); } -void UpdateWorker::serveDownloadFinish() -{ - downloadReply->close(); - downloadReply->deleteLater(); - if (downloadReply->error()) { - emit downloadError(downloadReply->errorString()); - } else { - emit downloadFinished(downloadFile.fileName()); - } -} - -void UpdateWorker::process(size_t bytesReceived, size_t bytesTotal) -{ - downloadFile.write(downloadReply->readAll()); - emit downloadProcess(bytesReceived, bytesTotal); -} - -QString UpdateWorker::getRepositeryExt() const -{ -# ifdef Q_OS_LINUX - return "AppImage"; -# elif defined(Q_OS_WIN64) || defined(Q_OS_WIN32) - return "zip"; -# elif defined(Q_OS_MACOS) - return "dmg"; -# endif -} - -QString UpdateWorker::getRepositoryFileName() const -{ - QString downloadFileName; -# ifdef Q_OS_LINUX - downloadFileName = "Cutter-v%1-x%2.Linux.AppImage"; -# elif defined(Q_OS_WIN64) || defined(Q_OS_WIN32) - downloadFileName = "Cutter-v%1-x%2.Windows.zip"; -# elif defined(Q_OS_MACOS) - downloadFileName = "Cutter-v%1-x%2.macOS.dmg"; -# endif - downloadFileName = - downloadFileName.arg(latestVersion.toString()) - .arg(QSysInfo::buildAbi().split('-').at(2).contains("64") ? "64" : "32"); - - return downloadFileName; -} - QVersionNumber UpdateWorker::currentVersionNumber() { return QVersionNumber(CUTTER_VERSION_MAJOR, CUTTER_VERSION_MINOR, CUTTER_VERSION_PATCH); diff --git a/src/common/UpdateWorker.h b/src/common/UpdateWorker.h index 61b8d038..46854f87 100644 --- a/src/common/UpdateWorker.h +++ b/src/common/UpdateWorker.h @@ -23,8 +23,7 @@ class QNetworkReply; /** * @class UpdateWorker - * @brief The UpdateWorker class is a class providing API to check for current Cutter version - * and download specific version of one. + * @brief The UpdateWorker class is a class providing API to check for current Cutter version. */ class UpdateWorker : public QObject @@ -47,23 +46,12 @@ public: void checkCurrentVersion(time_t timeoutMs); - /** - * @fn void UpdateWorker::download(QDir downloadPath, QString version) - * - * @brief Downloads provided @a version of Cutter into @a downloadDir. - * - * @sa downloadProcess(size_t bytesReceived, size_t bytesTotal) - */ - void download(QString filename, QString version); - /** * @fn void UpdateWorker::showUpdateDialog() * - * Shows dialog that allows user to either download latest version of Cutter from website - * or download it by clicking on a button. This dialog also has "Don't check for updates" - * button which disables on-start update checks if @a showDontCheckForUpdatesButton is true. - * - * @sa downloadProcess(size_t bytesReceived, size_t bytesTotal) + * Shows dialog that allows user to download latest version of Cutter from website. + * This dialog also has "Don't check for updates" button which disables on-start update + * checks if @a showDontCheckForUpdatesButton is true. */ void showUpdateDialog(bool showDontCheckForUpdatesButton); @@ -73,18 +61,6 @@ public: */ static QVersionNumber currentVersionNumber(); -public slots: - /** - * @fn void UpdateWorker::abortDownload() - * - * @brief Stops current process of downloading. - * - * @note UpdateWorker::downloadFinished(QString filename) is not send after this function. - * - * @sa download(QDir downloadDir, QString version) - */ - void abortDownload(); - signals: /** * @fn UpdateWorker::checkComplete(const QString& verson, const QString& errorMsg) @@ -95,46 +71,14 @@ signals: */ void checkComplete(const QVersionNumber &currVerson, const QString &errorMsg); - /** - * @fn UpdateWorker::downloadProcess(size_t bytesReceived, size_t bytesTotal) - * - * The signal is emitted each time when some amount of bytes was downloaded. - * May be used as indicator of download progress. - */ - void downloadProcess(size_t bytesReceived, size_t bytesTotal); - - /** - * @fn UpdateWorker::downloadFinished(QString filename) - * - * @brief The signal is emitted as soon as downloading completes. - */ - void downloadFinished(QString filename); - - /** - * @fn UpdateWorker::downloadError(QString errorStr) - * - * @brief The signal is emitted when error occures during download. - */ - void downloadError(QString errorStr); - private slots: void serveVersionCheckReply(); - void serveDownloadFinish(); - - void process(size_t bytesReceived, size_t bytesTotal); - -private: - QString getRepositeryExt() const; - QString getRepositoryFileName() const; - private: QNetworkAccessManager nm; QVersionNumber latestVersion; QTimer t; bool pending; - QFile downloadFile; - QNetworkReply *downloadReply; QNetworkReply *checkReply; }; From e4db94eb87d8f74571c0deacfd46dfc9dd844f9e Mon Sep 17 00:00:00 2001 From: billow Date: Sat, 2 Jul 2022 21:49:13 +0800 Subject: [PATCH 05/77] Convert to rizin APIs (#2973) `rz_core_bin_get_compile_time`, `ij`/`CutterCore::getFileInfo()`, `aaij`, `iVj`, `iEj`, `izzj`, `iMj`, `aeim-`, `aei-`, `tc`, `rz_core_config_variable_spaces`, `o`, `oodf` --- src/common/BugReporting.cpp | 24 +-- src/core/Cutter.cpp | 144 +++++++++++------- src/core/Cutter.h | 5 +- src/dialogs/VersionInfoDialog.cpp | 242 +++++++++++++++++++----------- src/widgets/Dashboard.cpp | 64 ++++---- src/widgets/FunctionsWidget.cpp | 13 +- src/widgets/TypesWidget.cpp | 8 +- 7 files changed, 310 insertions(+), 190 deletions(-) diff --git a/src/common/BugReporting.cpp b/src/common/BugReporting.cpp index 7d5ec919..18aa36ef 100644 --- a/src/common/BugReporting.cpp +++ b/src/common/BugReporting.cpp @@ -8,26 +8,18 @@ void openIssue() { + RzCoreLocked core(Core()); + RzBinFile *bf = rz_bin_cur(core->bin); + RzBinInfo *info = rz_bin_get_info(core->bin); + RzBinPlugin *plugin = rz_bin_file_cur_plugin(bf); + QString url, osInfo, format, arch, type; // Pull in info needed for git issue osInfo = QSysInfo::productType() + " " + (QSysInfo::productVersion() == "unknown" ? "" : QSysInfo::productVersion()); - CutterJson docu = Core()->getFileInfo(); - CutterJson coreObj = docu["core"]; - CutterJson binObj = docu["bin"]; - if (binObj.size()) { - format = coreObj["format"].toString(); - arch = binObj["arch"].toString(); - if (binObj["type"].valid()) { - type = coreObj["type"].toString(); - } else { - type = "N/A"; - } - } else { - format = coreObj["format"].toString(); - arch = "N/A"; - type = "N/A"; - } + format = plugin && RZ_STR_ISNOTEMPTY(plugin->name) ? plugin->name : "N/A"; + arch = info && RZ_STR_ISNOTEMPTY(info->arch) ? info->arch : "N/A"; + type = info && RZ_STR_ISNOTEMPTY(info->type) ? info->type : "N/A"; url = "https://github.com/rizinorg/cutter/issues/new?&body=**Environment information**\n* " "Operating System: " + osInfo + "\n* Cutter version: " + CUTTER_VERSION_FULL + "\n* Obtained from:\n" diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 61882cfc..f3c0f0de 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -681,7 +681,7 @@ bool CutterCore::mapFile(QString path, RVA mapaddr) { CORE_LOCK(); RVA addr = mapaddr != RVA_INVALID ? mapaddr : 0; - ut64 baddr = Core()->getFileInfo()["bin"]["baddr"].toUt64(); + ut64 baddr = rz_bin_get_baddr(core->bin); if (rz_core_file_open(core, path.toUtf8().constData(), RZ_PERM_RX, addr)) { rz_core_bin_load(core, path.toUtf8().constData(), baddr); } else { @@ -1434,16 +1434,6 @@ bool CutterCore::registerDecompiler(Decompiler *decompiler) return true; } -CutterJson CutterCore::getFileInfo() -{ - return cmdj("ij"); -} - -CutterJson CutterCore::getFileVersionInfo() -{ - return cmdj("iVj"); -} - QString CutterCore::getSignatureInfo() { CORE_LOCK(); @@ -1456,9 +1446,23 @@ QString CutterCore::getSignatureInfo() if (!signature) { return {}; } + auto sig = parseJson(signature, nullptr); + if (sig.size() == 0) { + return {}; + } return fromOwnedCharPtr(signature); } +bool CutterCore::existsFileInfo() +{ + CORE_LOCK(); + const RzBinInfo *info = rz_bin_get_info(core->bin); + if (!(info && info->rclass)) { + return false; + } + return strncmp("pe", info->rclass, 2) == 0 || strncmp("elf", info->rclass, 3) == 0; +} + // Utility function to check if a telescoped item exists and add it with prefixes to the desc static inline const QString appendVar(QString &dst, const QString val, const QString prepend_val, const QString append_val) @@ -2133,9 +2137,15 @@ void CutterCore::attachDebug(int pid) offsetPriorDebugging = getOffset(); } - QString attach_command = currentlyOpenFile.isEmpty() ? "o" : "oodf"; - // attach to process with dbg plugin - asyncCmd("e cfg.debug=true;" + attach_command + " dbg://" + QString::number(pid), debugTask); + CORE_LOCK(); + setConfig("cfg.debug", true); + auto uri = rz_str_newf("dbg://%d", pid); + if (currentlyOpenFile.isEmpty()) { + rz_core_file_open_load(core, uri, 0, RZ_PERM_R, false); + } else { + rz_core_file_reopen_remote_debug(core, uri, 0); + } + free(uri); emit debugTaskStateChanged(); @@ -2189,7 +2199,8 @@ void CutterCore::stopDebug() CORE_LOCK(); if (currentlyEmulating) { - cmdEsil("aeim- ; aei-"); + rz_core_analysis_esil_init_mem_del(core, NULL, UT64_MAX, UT32_MAX); + rz_core_analysis_esil_deinit(core); resetWriteCache(); rz_core_debug_clear_register_flags(core); rz_core_analysis_esil_trace_stop(core); @@ -3173,21 +3184,38 @@ QList CutterCore::getAllImports() QList CutterCore::getAllExports() { CORE_LOCK(); - QList ret; - - for (CutterJson exportObject : cmdj("iEj")) { - ExportDescription exp; - - exp.vaddr = exportObject[RJsonKey::vaddr].toRVA(); - exp.paddr = exportObject[RJsonKey::paddr].toRVA(); - exp.size = exportObject[RJsonKey::size].toRVA(); - exp.type = exportObject[RJsonKey::type].toString(); - exp.name = exportObject[RJsonKey::name].toString(); - exp.flag_name = exportObject[RJsonKey::flagname].toString(); - - ret << exp; + RzBinFile *bf = rz_bin_cur(core->bin); + if (!bf) { + return {}; + } + const RzList *symbols = rz_bin_object_get_symbols(bf->o); + if (!symbols) { + return {}; } + QString lang = getConfigi("bin.demangle") ? getConfig("bin.lang") : ""; + bool va = core->io->va || core->bin->is_debugger; + + QList ret; + for (const auto &symbol : CutterRzList(symbols)) { + if (!(symbol->name && rz_core_sym_is_export(symbol))) { + continue; + } + + RzBinSymNames sn = {}; + rz_core_sym_name_init(core, &sn, symbol, lang.isEmpty() ? NULL : lang.toUtf8().constData()); + + ExportDescription exportDescription; + exportDescription.vaddr = rva(bf->o, symbol->paddr, symbol->vaddr, va); + exportDescription.paddr = symbol->paddr; + exportDescription.size = symbol->size; + exportDescription.type = symbol->type; + exportDescription.name = sn.symbolname; + exportDescription.flag_name = sn.nameflag; + ret << exportDescription; + + rz_core_sym_name_fini(&sn); + } return ret; } @@ -3343,22 +3371,37 @@ QList CutterCore::getAllRelocs() QList CutterCore::getAllStrings() { - return parseStringsJson(cmdjTask("izzj")); -} + CORE_LOCK(); + RzBinFile *bf = rz_bin_cur(core->bin); + if (!bf) { + return {}; + } + RzBinObject *obj = rz_bin_cur_object(core->bin); + if (!obj) { + return {}; + } + RzList *l = rz_core_bin_whole_strings(core, bf); + if (!l) { + return {}; + } + + int va = core->io->va || core->bin->is_debugger; + RzStrEscOptions opt = {}; + opt.show_asciidot = false; + opt.esc_bslash = true; + opt.esc_double_quotes = true; -QList CutterCore::parseStringsJson(const CutterJson &doc) -{ QList ret; + for (const auto &str : CutterRzList(l)) { + auto section = obj ? rz_bin_get_section_at(obj, str->paddr, 0) : NULL; - for (CutterJson value : doc) { StringDescription string; - - string.string = value[RJsonKey::string].toString(); - string.vaddr = value[RJsonKey::vaddr].toRVA(); - string.type = value[RJsonKey::type].toString(); - string.size = value[RJsonKey::size].toUt64(); - string.length = value[RJsonKey::length].toUt64(); - string.section = value[RJsonKey::section].toString(); + string.string = rz_str_escape_utf8_keep_printable(str->string, &opt); + string.vaddr = obj ? rva(obj, str->paddr, str->vaddr, va) : str->paddr; + string.type = str->type; + string.size = str->size; + string.length = str->length; + string.section = section ? section->name : ""; ret << string; } @@ -3917,8 +3960,7 @@ QString CutterCore::getTypeAsC(QString name) return output; } char *earg = rz_cmd_escape_arg(name.toUtf8().constData(), RZ_CMD_ESCAPE_ONE_ARG); - // TODO: use API for `tc` command once available - QString result = cmd(QString("tc %1").arg(earg)); + QString result = fromOwnedCharPtr(rz_core_types_as_c(core, earg, true)); free(earg); return result; } @@ -4495,19 +4537,15 @@ QByteArray CutterCore::ioRead(RVA addr, int len) QStringList CutterCore::getConfigVariableSpaces(const QString &key) { CORE_LOCK(); - QStringList stringList; - for (const auto &node : CutterRzList(core->config->nodes)) { - stringList.push_back(node->name); + RzList *list = rz_core_config_in_space(core, key.toUtf8().constData()); + if (!list) { + return {}; } - if (!key.isEmpty()) { - stringList = stringList.filter(QRegularExpression(QString("^%0\\..*").arg(key))); - std::transform(stringList.begin(), stringList.end(), stringList.begin(), - [](const QString &x) { return x.split('.').last(); }); - } else { - std::transform(stringList.begin(), stringList.end(), stringList.begin(), - [](const QString &x) { return x.split('.').first(); }); + QStringList stringList; + for (const auto &x : CutterRzList(list)) { + stringList << x; } - stringList.removeDuplicates(); + rz_list_free(list); return stringList; } diff --git a/src/core/Cutter.h b/src/core/Cutter.h index b5be0ff8..9aa90b18 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -569,9 +569,8 @@ public: bool registerDecompiler(Decompiler *decompiler); RVA getOffsetJump(RVA addr); - CutterJson getFileInfo(); QString getSignatureInfo(); - CutterJson getFileVersionInfo(); + bool existsFileInfo(); void setGraphEmpty(bool empty); bool isGraphEmpty(); @@ -688,8 +687,6 @@ public: QList getXRefs(RVA addr, bool to, bool whole_function, const QString &filterType = QString()); - QList parseStringsJson(const CutterJson &doc); - void handleREvent(int type, void *data); /* Signals related */ diff --git a/src/dialogs/VersionInfoDialog.cpp b/src/dialogs/VersionInfoDialog.cpp index 7e67d594..dd43f23e 100644 --- a/src/dialogs/VersionInfoDialog.cpp +++ b/src/dialogs/VersionInfoDialog.cpp @@ -23,145 +23,213 @@ VersionInfoDialog::~VersionInfoDialog() {} void VersionInfoDialog::fillVersionInfo() { - - CutterJson doc = Core()->getFileVersionInfo(); - + RzCoreLocked core(Core()); + const RzBinInfo *info = rz_bin_get_info(core->bin); + if (!info || !info->rclass) { + return; + } // Case ELF - if (doc["verneed"].valid()) { - CutterJson verneed = doc["verneed"].first(); - CutterJson versym = doc["versym"].first(); - + if (strncmp("elf", info->rclass, 3) == 0) { // Set labels ui->leftLabel->setText("Version symbols"); ui->rightLabel->setText("Version need"); - // Left tree - QTreeWidgetItem *secNameItemL = new QTreeWidgetItem(); - secNameItemL->setText(0, "Section name:"); - secNameItemL->setText(1, versym["section_name"].toString()); - ui->leftTreeWidget->addTopLevelItem(secNameItemL); + Sdb *sdb = sdb_ns_path(core->sdb, "bin/cur/info/versioninfo/versym", 0); + if (!sdb) { + return; + } + // Left tree QTreeWidgetItem *addrItemL = new QTreeWidgetItem(); addrItemL->setText(0, "Address:"); - addrItemL->setText(1, RzAddressString(versym["address"].toRVA())); + addrItemL->setText(1, RzAddressString(sdb_num_get(sdb, "addr", 0))); ui->leftTreeWidget->addTopLevelItem(addrItemL); QTreeWidgetItem *offItemL = new QTreeWidgetItem(); offItemL->setText(0, "Offset:"); - offItemL->setText(1, RzAddressString(versym["offset"].toRVA())); + offItemL->setText(1, RzAddressString(sdb_num_get(sdb, "offset", 0))); ui->leftTreeWidget->addTopLevelItem(offItemL); - QTreeWidgetItem *linkItemL = new QTreeWidgetItem(); - linkItemL->setText(0, "Link:"); - linkItemL->setText(1, QString::number(versym["link"].toRVA())); - ui->leftTreeWidget->addTopLevelItem(linkItemL); - - QTreeWidgetItem *linkNameItemL = new QTreeWidgetItem(); - linkNameItemL->setText(0, "Link section name:"); - linkNameItemL->setText(1, versym["link_section_name"].toString()); - ui->leftTreeWidget->addTopLevelItem(linkNameItemL); - QTreeWidgetItem *entriesItemL = new QTreeWidgetItem(); entriesItemL->setText(0, "Entries:"); - for (CutterJson obj : versym["entries"]) { - QTreeWidgetItem *tempItem = new QTreeWidgetItem(); - tempItem->setText(0, RzAddressString(obj["idx"].toRVA())); - tempItem->setText(1, obj["value"].toString()); - entriesItemL->addChild(tempItem); + const ut64 num_entries = sdb_num_get(sdb, "num_entries", 0); + for (size_t i = 0; i < num_entries; ++i) { + auto key = QString("entry%0").arg(i); + const char *const value = sdb_const_get(sdb, key.toStdString().c_str(), 0); + if (!value) { + continue; + } + auto item = new QTreeWidgetItem(); + item->setText(0, RzAddressString(i)); + item->setText(1, value); + entriesItemL->addChild(item); } ui->leftTreeWidget->addTopLevelItem(entriesItemL); // Adjust columns to content qhelpers::adjustColumns(ui->leftTreeWidget, 0); + sdb = sdb_ns_path(core->sdb, "bin/cur/info/versioninfo/verneed", 0); // Right tree - QTreeWidgetItem *secNameItemR = new QTreeWidgetItem(); - secNameItemR->setText(0, "Section name:"); - secNameItemR->setText(1, verneed["section_name"].toString()); - ui->rightTreeWidget->addTopLevelItem(secNameItemR); - QTreeWidgetItem *addrItemR = new QTreeWidgetItem(); addrItemR->setText(0, "Address:"); - addrItemR->setText(1, RzAddressString(verneed["address"].toRVA())); + addrItemR->setText(1, RzAddressString(sdb_num_get(sdb, "addr", 0))); ui->rightTreeWidget->addTopLevelItem(addrItemR); QTreeWidgetItem *offItemR = new QTreeWidgetItem(); offItemR->setText(0, "Offset:"); - offItemR->setText(1, RzAddressString(verneed["offset"].toRVA())); + offItemR->setText(1, RzAddressString(sdb_num_get(sdb, "offset", 0))); ui->rightTreeWidget->addTopLevelItem(offItemR); - QTreeWidgetItem *linkItemR = new QTreeWidgetItem(); - linkItemR->setText(0, "Link:"); - linkItemR->setText(1, QString::number(verneed["link"].toSt64())); - ui->rightTreeWidget->addTopLevelItem(linkItemR); - - QTreeWidgetItem *linkNameItemR = new QTreeWidgetItem(); - linkNameItemR->setText(0, "Link section name:"); - linkNameItemR->setText(1, verneed["link_section_name"].toString()); - ui->rightTreeWidget->addTopLevelItem(linkNameItemR); - QTreeWidgetItem *entriesItemR = new QTreeWidgetItem(); entriesItemR->setText(0, "Entries:"); - for (CutterJson parentObj : verneed["entries"]) { - QTreeWidgetItem *parentItem = new QTreeWidgetItem(); - QString parentString; - parentItem->setText(0, RzAddressString(parentObj["idx"].toRVA())); - parentString.append("Version: " + QString::number(parentObj["vn_version"].toSt64()) - + "\t"); - parentString.append("File: " + parentObj["file_name"].toString()); - parentItem->setText(1, parentString); + for (size_t num_version = 0;; num_version++) { + auto path_version = + QString("bin/cur/info/versioninfo/verneed/version%0").arg(num_version); + sdb = sdb_ns_path(core->sdb, path_version.toStdString().c_str(), 0); + if (!sdb) { + break; + } + const char *filename = sdb_const_get(sdb, "file_name", 0); + auto *parentItem = new QTreeWidgetItem(); + parentItem->setText(0, RzAddressString(sdb_num_get(sdb, "idx", 0))); + parentItem->setText(1, + QString("Version: %0\t" + "File: %1") + .arg(QString::number(sdb_num_get(sdb, "vn_version", 0)), + QString(filename))); - for (CutterJson childObj : parentObj["vernaux"]) { - QTreeWidgetItem *childItem = new QTreeWidgetItem(); - QString childString; - childItem->setText(0, RzAddressString(childObj["idx"].toRVA())); - childString.append("Name: " + childObj["name"].toString() + "\t"); - childString.append("Flags: " + childObj["flags"].toString() + "\t"); - childString.append("Version: " + QString::number(childObj["version"].toSt64())); + int num_vernaux = 0; + while (true) { + auto path_vernaux = + QString("%0/vernaux%1").arg(path_version, QString::number(num_vernaux++)); + sdb = sdb_ns_path(core->sdb, path_vernaux.toStdString().c_str(), 0); + if (!sdb) { + break; + } + + auto *childItem = new QTreeWidgetItem(); + childItem->setText(0, RzAddressString(sdb_num_get(sdb, "idx", 0))); + QString childString = + QString("Name: %0\t" + "Flags: %1\t" + "Version: %2\t") + .arg(sdb_const_get(sdb, "name", 0), sdb_const_get(sdb, "flags", 0), + QString::number(sdb_num_get(sdb, "version", 0))); childItem->setText(1, childString); parentItem->addChild(childItem); } entriesItemR->addChild(parentItem); } - ui->rightTreeWidget->addTopLevelItem(entriesItemR); + ui->rightTreeWidget->addTopLevelItem(entriesItemR); // Adjust columns to content qhelpers::adjustColumns(ui->rightTreeWidget, 0); - } - // Case PE - else if (doc["VS_FIXEDFILEINFO"].valid()) { - CutterJson vs = doc["VS_FIXEDFILEINFO"]; - CutterJson strings = doc["StringTable"]; - + else if (strncmp("pe", info->rclass, 2) == 0) { // Set labels ui->leftLabel->setText("VS Fixed file info"); ui->rightLabel->setText("String table"); + Sdb *sdb = NULL; // Left tree - for (CutterJson property : vs) { - QTreeWidgetItem *tempItem = new QTreeWidgetItem(); - tempItem->setText(0, property.key()); - if (property.type() == RZ_JSON_INTEGER) - tempItem->setText(1, RzHexString(property.toRVA())); - else - tempItem->setText(1, property.toString()); - ui->leftTreeWidget->addTopLevelItem(tempItem); - - // Adjust columns to content - qhelpers::adjustColumns(ui->leftTreeWidget, 0); + auto path_version = QString("bin/cur/info/vs_version_info/VS_VERSIONINFO%0").arg(0); + auto path_fixedfileinfo = QString("%0/fixed_file_info").arg(path_version); + sdb = sdb_ns_path(core->sdb, path_fixedfileinfo.toStdString().c_str(), 0); + if (!sdb) { + return; } + ut32 file_version_ms = sdb_num_get(sdb, "FileVersionMS", 0); + ut32 file_version_ls = sdb_num_get(sdb, "FileVersionLS", 0); + auto file_version = QString("%0.%1.%2.%3") + .arg(file_version_ms >> 16) + .arg(file_version_ms & 0xFFFF) + .arg(file_version_ls >> 16) + .arg(file_version_ls & 0xFFFF); + ut32 product_version_ms = sdb_num_get(sdb, "ProductVersionMS", 0); + ut32 product_version_ls = sdb_num_get(sdb, "ProductVersionLS", 0); + auto product_version = QString("%0.%1.%2.%3") + .arg(product_version_ms >> 16) + .arg(product_version_ms & 0xFFFF) + .arg(product_version_ls >> 16) + .arg(product_version_ls & 0xFFFF); + + auto item = new QTreeWidgetItem(); + item->setText(0, "Signature"); + item->setText(1, RzHexString(sdb_num_get(sdb, "Signature", 0))); + ui->leftTreeWidget->addTopLevelItem(item); + + item = new QTreeWidgetItem(); + item->setText(0, "StrucVersion"); + item->setText(1, RzHexString(sdb_num_get(sdb, "StrucVersion", 0))); + ui->leftTreeWidget->addTopLevelItem(item); + + item = new QTreeWidgetItem(); + item->setText(0, "FileVersion"); + item->setText(1, file_version); + ui->leftTreeWidget->addTopLevelItem(item); + + item = new QTreeWidgetItem(); + item->setText(0, "ProductVersion"); + item->setText(1, product_version); + ui->leftTreeWidget->addTopLevelItem(item); + + item = new QTreeWidgetItem(); + item->setText(0, "FileFlagsMask"); + item->setText(1, RzHexString(sdb_num_get(sdb, "FileFlagsMask", 0))); + ui->leftTreeWidget->addTopLevelItem(item); + + item = new QTreeWidgetItem(); + item->setText(0, "FileFlags"); + item->setText(1, RzHexString(sdb_num_get(sdb, "FileFlags", 0))); + ui->leftTreeWidget->addTopLevelItem(item); + + item = new QTreeWidgetItem(); + item->setText(0, "FileOS"); + item->setText(1, RzHexString(sdb_num_get(sdb, "FileOS", 0))); + ui->leftTreeWidget->addTopLevelItem(item); + + item = new QTreeWidgetItem(); + item->setText(0, "FileType"); + item->setText(1, RzHexString(sdb_num_get(sdb, "FileType", 0))); + ui->leftTreeWidget->addTopLevelItem(item); + + item = new QTreeWidgetItem(); + item->setText(0, "FileSubType"); + item->setText(1, RzHexString(sdb_num_get(sdb, "FileSubType", 0))); + ui->leftTreeWidget->addTopLevelItem(item); + + // Adjust columns to content + qhelpers::adjustColumns(ui->leftTreeWidget, 0); // Right tree - for (CutterJson property : strings) { - QTreeWidgetItem *tempItem = new QTreeWidgetItem(); - tempItem->setText(0, property.key()); - tempItem->setText(1, property.toString()); - ui->rightTreeWidget->addTopLevelItem(tempItem); - - // Adjust columns to content - qhelpers::adjustColumns(ui->rightTreeWidget, 0); + for (int num_stringtable = 0;; num_stringtable++) { + auto path_stringtable = QString("%0/string_file_info/stringtable%1") + .arg(path_version) + .arg(num_stringtable); + sdb = sdb_ns_path(core->sdb, path_stringtable.toStdString().c_str(), 0); + if (!sdb) { + break; + } + for (int num_string = 0; sdb; num_string++) { + auto path_string = QString("%0/string%1").arg(path_stringtable).arg(num_string); + sdb = sdb_ns_path(core->sdb, path_string.toStdString().c_str(), 0); + if (!sdb) { + continue; + } + int lenkey = 0; + int lenval = 0; + ut8 *key_utf16 = sdb_decode(sdb_const_get(sdb, "key", 0), &lenkey); + ut8 *val_utf16 = sdb_decode(sdb_const_get(sdb, "value", 0), &lenval); + item = new QTreeWidgetItem(); + item->setText(0, QString::fromUtf16(reinterpret_cast(key_utf16))); + item->setText(1, QString::fromUtf16(reinterpret_cast(val_utf16))); + ui->rightTreeWidget->addTopLevelItem(item); + free(key_utf16); + free(val_utf16); + } } + qhelpers::adjustColumns(ui->rightTreeWidget, 0); } } diff --git a/src/widgets/Dashboard.cpp b/src/widgets/Dashboard.cpp index 66265d43..3715bc36 100644 --- a/src/widgets/Dashboard.cpp +++ b/src/widgets/Dashboard.cpp @@ -32,24 +32,27 @@ Dashboard::~Dashboard() {} void Dashboard::updateContents() { - CutterJson docu = Core()->getFileInfo(); - CutterJson item = docu["core"]; - CutterJson item2 = docu["bin"]; + RzCoreLocked core(Core()); + int fd = rz_io_fd_get_current(core->io); + RzIODesc *desc = rz_io_desc_get(core->io, fd); + setPlainText(this->ui->modeEdit, desc ? rz_str_rwx_i(desc->perm & RZ_PERM_RWX) : ""); - setPlainText(this->ui->modeEdit, item["mode"].toString()); - setPlainText(this->ui->compilationDateEdit, item2["compiled"].toString()); - - if (!item2["relro"].toString().isEmpty()) { - QString relro = item2["relro"].toString().section(QLatin1Char(' '), 0, 0); - relro[0] = relro[0].toUpper(); - setPlainText(this->ui->relroEdit, relro); - } else { - setPlainText(this->ui->relroEdit, "N/A"); + RzBinFile *bf = rz_bin_cur(core->bin); + if (bf) { + setPlainText(this->ui->compilationDateEdit, rz_core_bin_get_compile_time(bf)); + if (bf->o) { + char *relco_buf = sdb_get(bf->o->kv, "elf.relro", 0); + if (RZ_STR_ISNOTEMPTY(relco_buf)) { + QString relro = QString(relco_buf).section(QLatin1Char(' '), 0, 0); + relro[0] = relro[0].toUpper(); + setPlainText(this->ui->relroEdit, relro); + } else { + setPlainText(this->ui->relroEdit, "N/A"); + } + } } // Add file hashes, analysis info and libraries - RzCoreLocked core(Core()); - RzBinFile *bf = rz_bin_cur(core->bin); RzBinInfo *binInfo = rz_bin_get_info(core->bin); setPlainText(ui->fileEdit, binInfo ? binInfo->file : ""); @@ -111,16 +114,25 @@ void Dashboard::updateContents() hashesLayout->addRow(new QLabel(label), hashLineEdit); } - CutterJson analinfo = Core()->cmdj("aaij"); - setPlainText(ui->functionsLineEdit, QString::number(analinfo["fcns"].toSt64())); - setPlainText(ui->xRefsLineEdit, QString::number(analinfo["xrefs"].toSt64())); - setPlainText(ui->callsLineEdit, QString::number(analinfo["calls"].toSt64())); - setPlainText(ui->stringsLineEdit, QString::number(analinfo["strings"].toSt64())); - setPlainText(ui->symbolsLineEdit, QString::number(analinfo["symbols"].toSt64())); - setPlainText(ui->importsLineEdit, QString::number(analinfo["imports"].toSt64())); - setPlainText(ui->coverageLineEdit, QString::number(analinfo["covrage"].toSt64()) + " bytes"); - setPlainText(ui->codeSizeLineEdit, QString::number(analinfo["codesz"].toSt64()) + " bytes"); - setPlainText(ui->percentageLineEdit, QString::number(analinfo["percent"].toSt64()) + "%"); + st64 fcns = rz_list_length(core->analysis->fcns); + st64 strs = rz_flag_count(core->flags, "str.*"); + st64 syms = rz_flag_count(core->flags, "sym.*"); + st64 imps = rz_flag_count(core->flags, "sym.imp.*"); + st64 code = rz_core_analysis_code_count(core); + st64 covr = rz_core_analysis_coverage_count(core); + st64 call = rz_core_analysis_calls_count(core); + ut64 xrfs = rz_analysis_xrefs_count(core->analysis); + double precentage = (code > 0) ? (covr * 100.0 / code) : 0; + + setPlainText(ui->functionsLineEdit, QString::number(fcns)); + setPlainText(ui->xRefsLineEdit, QString::number(xrfs)); + setPlainText(ui->callsLineEdit, QString::number(call)); + setPlainText(ui->stringsLineEdit, QString::number(strs)); + setPlainText(ui->symbolsLineEdit, QString::number(syms)); + setPlainText(ui->importsLineEdit, QString::number(imps)); + setPlainText(ui->coverageLineEdit, QString::number(covr) + " bytes"); + setPlainText(ui->codeSizeLineEdit, QString::number(code) + " bytes"); + setPlainText(ui->percentageLineEdit, QString::number(precentage) + "%"); // dunno: why not label->setText(lines.join("\n")? while (ui->verticalLayout_2->count() > 0) { @@ -153,9 +165,7 @@ void Dashboard::updateContents() if (Core()->getSignatureInfo().isEmpty()) { ui->certificateButton->setEnabled(false); } - if (!Core()->getFileVersionInfo().size()) { - ui->versioninfoButton->setEnabled(false); - } + ui->versioninfoButton->setEnabled(Core()->existsFileInfo()); } void Dashboard::on_certificateButton_clicked() diff --git a/src/widgets/FunctionsWidget.cpp b/src/widgets/FunctionsWidget.cpp index 6324467b..b59cfb34 100644 --- a/src/widgets/FunctionsWidget.cpp +++ b/src/widgets/FunctionsWidget.cpp @@ -564,7 +564,18 @@ void FunctionsWidget::refreshTree() importAddresses.insert(import.plt); } - mainAdress = (ut64)Core()->cmdj("iMj")["vaddr"].toUt64(); + mainAdress = RVA_INVALID; + RzCoreLocked core(Core()); + RzBinFile *bf = rz_bin_cur(core->bin); + if (bf) { + const RzBinAddr *binmain = + rz_bin_object_get_special_symbol(bf->o, RZ_BIN_SPECIAL_SYMBOL_MAIN); + if (binmain) { + int va = core->io->va || core->bin->is_debugger; + mainAdress = va ? rz_bin_object_addr_with_base(bf->o, binmain->vaddr) + : binmain->paddr; + } + } functionModel->updateCurrentIndex(); functionModel->endResetModel(); diff --git a/src/widgets/TypesWidget.cpp b/src/widgets/TypesWidget.cpp index 8e7a0e6e..ebdeace1 100644 --- a/src/widgets/TypesWidget.cpp +++ b/src/widgets/TypesWidget.cpp @@ -259,6 +259,10 @@ void TypesWidget::showTypesContextMenu(const QPoint &pt) void TypesWidget::on_actionExport_Types_triggered() { + char *str = rz_core_types_as_c_all(Core()->core(), true); + if (!str) { + return; + } QString filename = QFileDialog::getSaveFileName(this, tr("Save File"), Config()->getRecentFolder()); if (filename.isEmpty()) { @@ -272,8 +276,8 @@ void TypesWidget::on_actionExport_Types_triggered() return; } QTextStream fileOut(&file); - // TODO: use API for `tc` command once available - fileOut << Core()->cmd("tc"); + fileOut << str; + free(str); file.close(); } From 3855576be52bb4447946872db8e61661c749dcb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Mon, 4 Jul 2022 10:39:10 +0200 Subject: [PATCH 06/77] Use meson==0.61.5 to fix libzip strcasecmp errors (#2991) --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 4adc9fd1..257f7469 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -71,7 +71,7 @@ jobs: - name: py dependencies run: | python3 -m pip install -U pip==21.3.1 - pip install meson + pip install meson==0.61.5 # https://github.com/rizinorg/cutter/runs/7170222817?check_suite_focus=true - name: Prepare package id shell: bash run: | From 569206fec8c5241c1c5b69faf40f84cd84d63148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Mon, 4 Jul 2022 13:01:38 +0200 Subject: [PATCH 07/77] Update cutter-deps to v15 with macOS/arm64 included (#2990) --- scripts/fetch_deps.sh | 59 ++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/scripts/fetch_deps.sh b/scripts/fetch_deps.sh index 4079c6aa..fedace6d 100755 --- a/scripts/fetch_deps.sh +++ b/scripts/fetch_deps.sh @@ -1,54 +1,55 @@ #!/bin/bash +set -e + cd $(dirname "${BASH_SOURCE[0]}")/.. mkdir -p cutter-deps && cd cutter-deps -LINUX_FILE="cutter-deps-linux.tar.gz" -LINUX_MD5=eb2710548d951823e6b5340c33c8fc99 -LINUX_URL=https://github.com/rizinorg/cutter-deps/releases/download/v14/cutter-deps-linux.tar.gz +DEPS_FILE_linux_x86_64=cutter-deps-linux-x86_64.tar.gz +DEPS_SHA256_linux_x86_64=0721c85548bbcf31f6911cdb2227e5efb4a20c34262672d4cd2193db166b2f8c -MACOS_FILE="cutter-deps-macos.tar.gz" -MACOS_MD5=f921c007430eec38b06acef8cc0fe42a -MACOS_URL=https://github.com/rizinorg/cutter-deps/releases/download/v14/cutter-deps-macos.tar.gz +DEPS_FILE_macos_x86_64=cutter-deps-macos-x86_64.tar.gz +DEPS_SHA256_macos_x86_64=0a23fdec3012a8af76675d6f3ff39cf9df9b08c13d1156fb7ffcc0e495c9407f -WIN_FILE="cutter-deps-win.tar.gz" -WIN_MD5=09aa544e62cdd786df3598f1ff340f9e -WIN_URL=https://github.com/rizinorg/cutter-deps/releases/download/v14/cutter-deps-win.tar.gz +DEPS_FILE_macos_arm64=cutter-deps-macos-arm64.tar.gz +DEPS_SHA256_macos_arm64=f9b9a5569bd23c9b5e45836b82aba7576a5c53df4871380a55c370b9d7f88615 +DEPS_FILE_win_x86_64=cutter-deps-win-x86_64.tar.gz +DEPS_SHA256_win_x86_64=9ab4e89732a3df0859a26fd5de6d9f3cb80106cbe2539340af831ed298625076 + +DEPS_BASE_URL=https://github.com/rizinorg/cutter-deps/releases/download/v15 + +ARCH=x86_64 if [ "$OS" == "Windows_NT" ]; then - FILE="${WIN_FILE}" - MD5="${WIN_MD5}" - URL="${WIN_URL}" + PLATFORM=win else UNAME_S="$(uname -s)" if [ "$UNAME_S" == "Linux" ]; then - FILE="${LINUX_FILE}" - MD5="${LINUX_MD5}" - URL="${LINUX_URL}" + PLATFORM=linux elif [ "$UNAME_S" == "Darwin" ]; then - FILE="${MACOS_FILE}" - MD5="${MACOS_MD5}" - URL="${MACOS_URL}" + PLATFORM=macos + ARCH=$(uname -m) else echo "Unsupported Platform: uname -s => $UNAME_S, \$OS => $OS" exit 1 fi fi -curl -L "$URL" -o "$FILE" || exit 1 +DEPS_FILE=DEPS_FILE_${PLATFORM}_${ARCH} +DEPS_FILE=${!DEPS_FILE} +DEPS_SHA256=DEPS_SHA256_${PLATFORM}_${ARCH} +DEPS_SHA256=${!DEPS_SHA256} +DEPS_URL=${DEPS_BASE_URL}/${DEPS_FILE} -if [ "$UNAME_S" == "Darwin" ]; then - if [ "$(md5 -r "$FILE")" != "$MD5 $FILE" ]; then \ - echo "MD5 mismatch for file $FILE"; \ - exit 1; \ - else \ - echo "$FILE OK"; \ - fi -else - echo "$MD5 $FILE" | md5sum -c - || exit 1 +SHA256SUM=sha256sum +if ! command -v ${SHA256SUM} &> /dev/null; then + SHA256SUM="shasum -a 256" fi -tar -xf "$FILE" || exit 1 +curl -L "$DEPS_URL" -o "$DEPS_FILE" || exit 1 +echo "$DEPS_SHA256 $DEPS_FILE" | ${SHA256SUM} -c - || exit 1 + +tar -xf "$DEPS_FILE" || exit 1 if [ -f relocate.sh ]; then ./relocate.sh || exit 1 From 2c778d9b825145bf7617d0d64679140a221363ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Tue, 5 Jul 2022 14:06:06 +0200 Subject: [PATCH 08/77] Add Woodpecker macOS/arm64 CI (#2992) Package names have also been updated to the scheme used since v2.1.0, to better represent different architectures. --- .github/workflows/ccpp.yml | 6 ++--- .woodpecker/macos-arm64.yml | 49 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 .woodpecker/macos-arm64.yml diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 257f7469..ff9bde5f 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -156,7 +156,7 @@ jobs: -ignore-glob=usr/lib/python3.9/**/* \ -verbose=2 find ./appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq - export APPIMAGE_FILE="Cutter-${PACKAGE_ID}-x64.Linux.AppImage" + export APPIMAGE_FILE="Cutter-${PACKAGE_ID}-Linux-x86_64.AppImage" mv Cutter-*-x86_64.AppImage "$APPIMAGE_FILE" echo PACKAGE_NAME=$APPIMAGE_FILE >> $GITHUB_ENV echo UPLOAD_ASSET_TYPE=application/x-executable >> $GITHUB_ENV @@ -173,7 +173,7 @@ jobs: source scripts/prepare_breakpad_macos.sh mkdir build cd build - PACKAGE_NAME=Cutter-${PACKAGE_ID}-x64.macOS + PACKAGE_NAME=Cutter-${PACKAGE_ID}-macOS-x86_64 cmake \ -DCMAKE_BUILD_TYPE=Release \ -DPYTHON_LIBRARY="$CUTTER_DEPS_PYTHON_PREFIX/lib/libpython3.9.dylib" \ @@ -219,7 +219,7 @@ jobs: cd mkdir build cd build - set PACKAGE_NAME=cutter-%PACKAGE_ID%-x64.Windows + set PACKAGE_NAME=Cutter-%PACKAGE_ID%-Windows-x86_64 cmake ^ -DCMAKE_BUILD_TYPE=Release ^ -DCUTTER_USE_BUNDLED_RIZIN=ON ^ diff --git a/.woodpecker/macos-arm64.yml b/.woodpecker/macos-arm64.yml new file mode 100644 index 00000000..96ce1457 --- /dev/null +++ b/.woodpecker/macos-arm64.yml @@ -0,0 +1,49 @@ +platform: darwin/arm64 + +pipeline: + fetch-deps: + image: /bin/bash + commands: + - scripts/fetch_deps.sh + build: + image: /bin/bash + commands: + - set -e + - export PACKAGE_ID=${CI_COMMIT_TAG=git-`date "+%Y-%m-%d"`-${CI_COMMIT_SHA}} + - export PACKAGE_NAME=Cutter-$${PACKAGE_ID}-macOS-arm64 + - source cutter-deps/env.sh + - source scripts/prepare_breakpad_macos.sh + - cmake -Bbuild -GNinja + -DCMAKE_BUILD_TYPE=Release + -DPYTHON_LIBRARY="$$CUTTER_DEPS_PYTHON_PREFIX/lib/libpython3.9.dylib" + -DPYTHON_INCLUDE_DIR="$$CUTTER_DEPS_PYTHON_PREFIX/include/python3.9" + -DPYTHON_EXECUTABLE="$$CUTTER_DEPS_PYTHON_PREFIX/bin/python3" + -DCUTTER_ENABLE_PYTHON=ON + -DCUTTER_ENABLE_PYTHON_BINDINGS=ON + -DCUTTER_ENABLE_CRASH_REPORTS=ON + -DCUTTER_USE_BUNDLED_RIZIN=ON + -DCUTTER_ENABLE_PACKAGING=ON + -DCUTTER_ENABLE_SIGDB=ON + -DCUTTER_PACKAGE_DEPENDENCIES=ON + -DCUTTER_ENABLE_DEPENDENCY_DOWNLOADS=ON + -DCUTTER_PACKAGE_RZ_GHIDRA=ON + -DCUTTER_PACKAGE_JSDEC=ON + -DCUTTER_PACKAGE_RZ_LIBSWIFT=ON + -DCUTTER_PACKAGE_RZ_LIBYARA=ON + -DCPACK_PACKAGE_FILE_NAME="$$PACKAGE_NAME" + -DCMAKE_FRAMEWORK_PATH="$$BREAKPAD_FRAMEWORK_DIR" + -DCPACK_BUNDLE_APPLE_CERT_APP="-" + - ninja -C build + package: + image: /bin/bash + commands: + - source cutter-deps/env.sh + - ninja -C build package + deploy: + when: + event: tag + tag: v* + image: /bin/bash + commands: + - gh release upload "${CI_COMMIT_TAG}" build/Cutter-*.dmg + secrets: [ github_token ] From 51c0a3d469df45387dab329459454b24a655ff7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 9 Jul 2022 12:48:48 +0200 Subject: [PATCH 09/77] Construct and destruct CutterCore singleton locally (Fix #2704) (#2994) Using Q_GLOBAL_STATIC meant that the CutterCore was destructed late as part of a binary destructor. It would then free the RzCore, calling for example the fini callbacks of all plugins. However global destructors in shared library plugins may have already been run at this point, leading to for example rz-ghidra's decompiler_mutex being used after destruction. Instead of the Q_GLOBAL_STATIC-managed global object, we are now handling the lifetime of the CutterCore ourselves and only injecting its instance to be accessed globally. This can also be a first step towards making the core instance completely local. --- src/CutterApplication.h | 1 + src/core/Cutter.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/CutterApplication.h b/src/CutterApplication.h index f8dc01e3..cd27a26a 100644 --- a/src/CutterApplication.h +++ b/src/CutterApplication.h @@ -50,6 +50,7 @@ private: private: bool m_FileAlreadyDropped; + CutterCore core; MainWindow *mainWindow; CutterCommandLineOptions clOptions; }; diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index f3c0f0de..e2d85297 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -24,7 +24,7 @@ #include #include -Q_GLOBAL_STATIC(CutterCore, uniqueInstance) +static CutterCore *uniqueInstance; #define RZ_JSON_KEY(name) static const QString name = QStringLiteral(#name) @@ -182,6 +182,10 @@ CutterCore::CutterCore(QObject *parent) coreMutex(QMutex::Recursive) #endif { + if (uniqueInstance) { + throw std::logic_error("Only one instance of CutterCore must exist"); + } + uniqueInstance = this; } CutterCore *CutterCore::instance() @@ -238,6 +242,8 @@ CutterCore::~CutterCore() rz_core_task_sync_end(&core_->tasks); rz_core_free(this->core_); rz_cons_free(); + assert(uniqueInstance == this); + uniqueInstance = nullptr; } RzCoreLocked CutterCore::core() From db0d4d85a600eddec46f1ac3d9b98f133e187ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 9 Jul 2022 16:26:35 +0200 Subject: [PATCH 10/77] Use CutterJson for Signatures and rewrite JsonModel (#2997) This fixes a double-free in getSignatureInfo() when the string was already freed by the intermediate CutterJson. But actually just using this CutterJson makes more sense than the string anyway, so let's do that. The old JsonModel rebuilt the entire structure, defeating the main purpose of a custom model in comparison to the existing QTreeWidget. And since we are holding the json structures ourselves anyway, such a custom model does not have many benefits anyway. --- src/CMakeLists.txt | 2 - src/common/JsonModel.cpp | 149 +++++++-------------------------- src/common/JsonModel.h | 32 +------ src/common/JsonTreeItem.cpp | 104 ----------------------- src/common/JsonTreeItem.h | 39 --------- src/core/Cutter.cpp | 8 +- src/core/Cutter.h | 2 +- src/core/CutterJson.h | 1 - src/widgets/CutterTreeView.cpp | 9 +- src/widgets/CutterTreeView.h | 2 + src/widgets/Dashboard.cpp | 48 +++++------ 11 files changed, 67 insertions(+), 329 deletions(-) delete mode 100644 src/common/JsonTreeItem.cpp delete mode 100644 src/common/JsonTreeItem.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 47357c69..ba6691c9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -72,7 +72,6 @@ set(SOURCES widgets/CutterTreeWidget.cpp widgets/GraphWidget.cpp widgets/OverviewWidget.cpp - common/JsonTreeItem.cpp common/JsonModel.cpp dialogs/VersionInfoDialog.cpp widgets/FlirtWidget.cpp @@ -222,7 +221,6 @@ set(HEADER_FILES widgets/CutterTreeWidget.h widgets/GraphWidget.h widgets/OverviewWidget.h - common/JsonTreeItem.h common/JsonModel.h dialogs/VersionInfoDialog.h widgets/FlirtWidget.h diff --git a/src/common/JsonModel.cpp b/src/common/JsonModel.cpp index 4b1ef628..4bc01cb4 100644 --- a/src/common/JsonModel.cpp +++ b/src/common/JsonModel.cpp @@ -1,123 +1,38 @@ #include "JsonModel.h" -#include - -JsonModel::JsonModel(QObject *parent) : QAbstractItemModel(parent) +QTreeWidgetItem *Cutter::jsonTreeWidgetItem(const QString &key, const CutterJson &json) { - mRootItem = new JsonTreeItem; - mHeaders.append("key"); - mHeaders.append("value"); -} - -JsonModel::~JsonModel() -{ - delete mRootItem; -} - -bool JsonModel::load(QIODevice *device) -{ - return loadJson(device->readAll()); -} - -bool JsonModel::loadJson(const QByteArray &json) -{ - mDocument = QJsonDocument::fromJson(json); - - if (!mDocument.isNull()) { - beginResetModel(); - delete mRootItem; - if (mDocument.isArray()) { - mRootItem = JsonTreeItem::load(QJsonValue(mDocument.array())); - } else { - mRootItem = JsonTreeItem::load(QJsonValue(mDocument.object())); + QString val; + switch (json.type()) { + case RZ_JSON_STRING: + val = json.toString(); + break; + case RZ_JSON_BOOLEAN: + val = json.toBool() ? "true" : "false"; + break; + case RZ_JSON_DOUBLE: + val = QString::number(json.lowLevelValue()->num.dbl_value); + break; + case RZ_JSON_INTEGER: + val = QString::number(json.toUt64()); + break; + case RZ_JSON_NULL: + val = "null"; + break; + case RZ_JSON_OBJECT: + case RZ_JSON_ARRAY: + break; + } + auto r = new QTreeWidgetItem(QStringList({ key, val })); + if (json.type() == RZ_JSON_ARRAY) { + size_t i = 0; + for (const auto &child : json) { + r->addChild(jsonTreeWidgetItem(QString::number(i++), child)); + } + } else if (json.type() == RZ_JSON_OBJECT) { + for (const auto &child : json) { + r->addChild(jsonTreeWidgetItem(child.key(), child)); } - endResetModel(); - return true; } - return false; -} - -QVariant JsonModel::data(const QModelIndex &index, int role) const -{ - - if (!index.isValid()) - return QVariant(); - - JsonTreeItem *item = static_cast(index.internalPointer()); - - if (role == Qt::DisplayRole) { - - if (index.column() == 0) - return QString("%1").arg(item->key()); - - if (index.column() == 1) - return QString("%1").arg(item->value()); - } - - return QVariant(); -} - -QVariant JsonModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (role != Qt::DisplayRole) - return QVariant(); - - if (orientation == Qt::Horizontal) { - - return mHeaders.value(section); - } else - return QVariant(); -} - -QModelIndex JsonModel::index(int row, int column, const QModelIndex &parent) const -{ - if (!hasIndex(row, column, parent)) - return QModelIndex(); - - JsonTreeItem *parentItem; - - if (!parent.isValid()) - parentItem = mRootItem; - else - parentItem = static_cast(parent.internalPointer()); - - JsonTreeItem *childItem = parentItem->child(row); - if (childItem) - return createIndex(row, column, childItem); - else - return QModelIndex(); -} - -QModelIndex JsonModel::parent(const QModelIndex &index) const -{ - if (!index.isValid()) - return QModelIndex(); - - JsonTreeItem *childItem = static_cast(index.internalPointer()); - JsonTreeItem *parentItem = childItem->parent(); - - if (parentItem == mRootItem) - return QModelIndex(); - - return createIndex(parentItem->row(), 0, parentItem); -} - -int JsonModel::rowCount(const QModelIndex &parent) const -{ - JsonTreeItem *parentItem; - if (parent.column() > 0) - return 0; - - if (!parent.isValid()) - parentItem = mRootItem; - else - parentItem = static_cast(parent.internalPointer()); - - return parentItem->childCount(); -} - -int JsonModel::columnCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent) - return 2; + return r; } diff --git a/src/common/JsonModel.h b/src/common/JsonModel.h index 4b5c7cdd..5af02ab6 100644 --- a/src/common/JsonModel.h +++ b/src/common/JsonModel.h @@ -2,37 +2,13 @@ #ifndef JSONMODEL_H #define JSONMODEL_H -#include -#include -#include -#include -#include -#include +#include +#include "CutterJson.h" -#include "JsonTreeItem.h" +namespace Cutter { -class JsonTreeItem; +QTreeWidgetItem *jsonTreeWidgetItem(const QString &key, const CutterJson &json); -class JsonModel : public QAbstractItemModel -{ - -public: - explicit JsonModel(QObject *parent = nullptr); - bool load(QIODevice *device); - bool loadJson(const QByteArray &json); - QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; - QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; - QModelIndex index(int row, int column, - const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; - int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - ~JsonModel(); - -private: - JsonTreeItem *mRootItem; - QJsonDocument mDocument; - QStringList mHeaders; }; #endif // JSONMODEL_H diff --git a/src/common/JsonTreeItem.cpp b/src/common/JsonTreeItem.cpp deleted file mode 100644 index 3a16dbd7..00000000 --- a/src/common/JsonTreeItem.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "JsonTreeItem.h" - -JsonTreeItem::JsonTreeItem(JsonTreeItem *parent) -{ - mParent = parent; -} - -JsonTreeItem::~JsonTreeItem() -{ - qDeleteAll(mChilds); -} - -void JsonTreeItem::appendChild(JsonTreeItem *item) -{ - mChilds.append(item); -} - -JsonTreeItem *JsonTreeItem::child(int row) -{ - return mChilds.value(row); -} - -JsonTreeItem *JsonTreeItem::parent() -{ - return mParent; -} - -int JsonTreeItem::childCount() const -{ - return mChilds.count(); -} - -int JsonTreeItem::row() const -{ - if (mParent) - return mParent->mChilds.indexOf(const_cast(this)); - - return 0; -} - -void JsonTreeItem::setKey(const QString &key) -{ - mKey = key; -} - -void JsonTreeItem::setValue(const QString &value) -{ - mValue = value; -} - -void JsonTreeItem::setType(const QJsonValue::Type &type) -{ - mType = type; -} - -QString JsonTreeItem::key() const -{ - return mKey; -} - -QString JsonTreeItem::value() const -{ - return mValue; -} - -QJsonValue::Type JsonTreeItem::type() const -{ - return mType; -} - -JsonTreeItem *JsonTreeItem::load(const QJsonValue &value, JsonTreeItem *parent) -{ - JsonTreeItem *rootItem = new JsonTreeItem(parent); - rootItem->setKey("root"); - - if (value.isObject()) { - - // Get all QJsonValue childs - for (const QString &key : value.toObject().keys()) { - QJsonValue v = value.toObject().value(key); - JsonTreeItem *child = load(v, rootItem); - child->setKey(key); - child->setType(v.type()); - rootItem->appendChild(child); - } - - } else if (value.isArray()) { - // Get all QJsonValue childs - int index = 0; - for (const QJsonValue &v : value.toArray()) { - - JsonTreeItem *child = load(v, rootItem); - child->setKey(QString::number(index)); - child->setType(v.type()); - rootItem->appendChild(child); - ++index; - } - } else { - rootItem->setValue(value.toVariant().toString()); - rootItem->setType(value.type()); - } - - return rootItem; -} diff --git a/src/common/JsonTreeItem.h b/src/common/JsonTreeItem.h deleted file mode 100644 index 780a3799..00000000 --- a/src/common/JsonTreeItem.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef JSONTREEITEM_H -#define JSONTREEITEM_H - -#include -#include -#include -#include -#include -#include - -#include "JsonModel.h" - -class JsonTreeItem -{ -public: - JsonTreeItem(JsonTreeItem *parent = nullptr); - ~JsonTreeItem(); - void appendChild(JsonTreeItem *item); - JsonTreeItem *child(int row); - JsonTreeItem *parent(); - int childCount() const; - int row() const; - void setKey(const QString &key); - void setValue(const QString &value); - void setType(const QJsonValue::Type &type); - QString key() const; - QString value() const; - QJsonValue::Type type() const; - static JsonTreeItem *load(const QJsonValue &value, JsonTreeItem *parent = nullptr); - -private: - QString mKey; - QString mValue; - QJsonValue::Type mType; - QList mChilds; - JsonTreeItem *mParent; -}; - -#endif // JSONTREEITEM_H diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index e2d85297..a8a8c038 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -1440,7 +1440,7 @@ bool CutterCore::registerDecompiler(Decompiler *decompiler) return true; } -QString CutterCore::getSignatureInfo() +CutterJson CutterCore::getSignatureInfo() { CORE_LOCK(); RzBinFile *cur = rz_bin_cur(core->bin); @@ -1452,11 +1452,7 @@ QString CutterCore::getSignatureInfo() if (!signature) { return {}; } - auto sig = parseJson(signature, nullptr); - if (sig.size() == 0) { - return {}; - } - return fromOwnedCharPtr(signature); + return parseJson(signature, nullptr); } bool CutterCore::existsFileInfo() diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 9aa90b18..bdddd342 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -569,7 +569,7 @@ public: bool registerDecompiler(Decompiler *decompiler); RVA getOffsetJump(RVA addr); - QString getSignatureInfo(); + CutterJson getSignatureInfo(); bool existsFileInfo(); void setGraphEmpty(bool empty); bool isGraphEmpty(); diff --git a/src/core/CutterJson.h b/src/core/CutterJson.h index e8dd9819..e733ca6c 100644 --- a/src/core/CutterJson.h +++ b/src/core/CutterJson.h @@ -70,7 +70,6 @@ public: iterator end() const { return iterator(nullptr, nullptr); } bool toBool() const { return value && value->type == RZ_JSON_BOOLEAN && value->num.u_value; } - QString toJson() const { return rz_json_as_string(value); } st64 toSt64() const { return value && value->type == RZ_JSON_INTEGER ? value->num.s_value : 0; } ut64 toUt64() const { return value && value->type == RZ_JSON_INTEGER ? value->num.u_value : 0; } diff --git a/src/widgets/CutterTreeView.cpp b/src/widgets/CutterTreeView.cpp index f26fc08c..233cdd80 100644 --- a/src/widgets/CutterTreeView.cpp +++ b/src/widgets/CutterTreeView.cpp @@ -4,8 +4,13 @@ CutterTreeView::CutterTreeView(QWidget *parent) : QTreeView(parent), ui(new Ui::CutterTreeView()) { ui->setupUi(this); - this->setSelectionMode(QAbstractItemView::ExtendedSelection); - this->setUniformRowHeights(true); + applyCutterStyle(this); } CutterTreeView::~CutterTreeView() {} + +void CutterTreeView::applyCutterStyle(QTreeView *view) +{ + view->setSelectionMode(QAbstractItemView::ExtendedSelection); + view->setUniformRowHeights(true); +} diff --git a/src/widgets/CutterTreeView.h b/src/widgets/CutterTreeView.h index 46dfcccd..33600bc3 100644 --- a/src/widgets/CutterTreeView.h +++ b/src/widgets/CutterTreeView.h @@ -19,6 +19,8 @@ public: explicit CutterTreeView(QWidget *parent = nullptr); ~CutterTreeView(); + static void applyCutterStyle(QTreeView *view); + private: std::unique_ptr ui; }; diff --git a/src/widgets/Dashboard.cpp b/src/widgets/Dashboard.cpp index 3715bc36..f73b4cc1 100644 --- a/src/widgets/Dashboard.cpp +++ b/src/widgets/Dashboard.cpp @@ -2,7 +2,6 @@ #include "ui_Dashboard.h" #include "common/Helpers.h" #include "common/JsonModel.h" -#include "common/JsonTreeItem.h" #include "common/TempConfig.h" #include "dialogs/VersionInfoDialog.h" @@ -162,7 +161,7 @@ void Dashboard::updateContents() ui->verticalLayout_2->addSpacerItem(spacer); // Check if signature info and version info available - if (Core()->getSignatureInfo().isEmpty()) { + if (!Core()->getSignatureInfo().size()) { ui->certificateButton->setEnabled(false); } ui->versioninfoButton->setEnabled(Core()->existsFileInfo()); @@ -170,33 +169,24 @@ void Dashboard::updateContents() void Dashboard::on_certificateButton_clicked() { - static QDialog *viewDialog = nullptr; - static CutterTreeView *view = nullptr; - static JsonModel *model = nullptr; - static QString qstrCertificates; - if (!viewDialog) { - viewDialog = new QDialog(this); - view = new CutterTreeView(viewDialog); - model = new JsonModel(); - qstrCertificates = Core()->getSignatureInfo(); - } - if (!viewDialog->isVisible()) { - std::string strCertificates = qstrCertificates.toUtf8().constData(); - model->loadJson(QByteArray::fromStdString(strCertificates)); - view->setModel(model); - view->expandAll(); - view->resize(900, 600); - QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - sizePolicy.setHeightForWidth(view->sizePolicy().hasHeightForWidth()); - viewDialog->setSizePolicy(sizePolicy); - viewDialog->setMinimumSize(QSize(900, 600)); - viewDialog->setMaximumSize(QSize(900, 600)); - viewDialog->setSizeGripEnabled(false); - viewDialog->setWindowTitle("Certificates"); - viewDialog->show(); - } + QDialog dialog(this); + auto view = new QTreeWidget(&dialog); + view->setHeaderLabels({ tr("Key"), tr("Value") }); + view->addTopLevelItem(Cutter::jsonTreeWidgetItem(QString("<%1>").arg(tr("root")), + Core()->getSignatureInfo())); + CutterTreeView::applyCutterStyle(view); + view->expandAll(); + view->resize(900, 600); + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(view->sizePolicy().hasHeightForWidth()); + dialog.setSizePolicy(sizePolicy); + dialog.setMinimumSize(QSize(900, 600)); + dialog.setMaximumSize(QSize(900, 600)); + dialog.setSizeGripEnabled(false); + dialog.setWindowTitle("Certificates"); + dialog.exec(); } void Dashboard::on_versioninfoButton_clicked() From 44b7a7997a70d5537d3f02223c103718e15bd93f Mon Sep 17 00:00:00 2001 From: billow Date: Mon, 1 Aug 2022 23:57:16 +0800 Subject: [PATCH 11/77] Convert disassembly (pd pdj pdJ) to rizin API (#2996) --- rizin | 2 +- src/core/Cutter.cpp | 101 ++++++++++++++++++++------- src/core/Cutter.h | 4 ++ src/dialogs/XrefsDialog.cpp | 14 +++- src/menus/DisassemblyContextMenu.cpp | 24 ++++--- 5 files changed, 105 insertions(+), 40 deletions(-) diff --git a/rizin b/rizin index 0fc9c968..b79201b4 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit 0fc9c9682e8a74245e4f24b84508f2bb6b185328 +Subproject commit b79201b49a0d687c854b6e2f0c88fc8a7f2afa61 diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index a8a8c038..1cd9ad50 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -377,6 +377,37 @@ QString CutterCore::cmd(const char *str) return o; } +QString CutterCore::getFunctionExecOut(const std::function &fcn, const RVA addr) +{ + CORE_LOCK(); + + RVA offset = core->offset; + seekSilent(addr); + QString o = {}; + rz_cons_push(); + bool is_pipe = core->is_pipe; + core->is_pipe = true; + + if (!fcn(core)) { + core->is_pipe = is_pipe; + rz_cons_pop(); + goto clean_return; + } + + core->is_pipe = is_pipe; + rz_cons_filter(); + o = rz_cons_get_buffer(); + + rz_cons_pop(); + rz_cons_echo(NULL); + +clean_return: + if (offset != core->offset) { + seekSilent(offset); + } + return o; +} + bool CutterCore::isRedirectableDebugee() { if (!currentlyDebugging || currentlyAttachedToPID != -1) { @@ -1055,24 +1086,23 @@ RVA CutterCore::prevOpAddr(RVA startAddr, int count) RVA CutterCore::nextOpAddr(RVA startAddr, int count) { CORE_LOCK(); - - CutterJson array = - Core()->cmdj("pdj " + QString::number(count + 1) + " @ " + QString::number(startAddr)); - if (!array.size()) { - return startAddr + 1; + auto vec = reinterpret_cast(returnAtSeek( + [&]() { + return rz_core_analysis_bytes(core, core->block, (int)core->blocksize, count + 1); + }, + startAddr)); + RVA addr = startAddr + 1; + if (!vec) { + return addr; } - - CutterJson instValue = array.last(); - if (instValue.type() != RZ_JSON_OBJECT) { - return startAddr + 1; + auto ab = reinterpret_cast(rz_pvector_tail(vec)); + if (!(ab && ab->op)) { + rz_pvector_free(vec); + return addr; } - - RVA offset = instValue[RJsonKey::offset].toRVA(); - if (offset == RVA_INVALID) { - return startAddr + 1; - } - - return offset; + addr = ab->op->addr; + rz_pvector_free(vec); + return addr; } RVA CutterCore::getOffset() @@ -4176,18 +4206,35 @@ void CutterCore::loadPDB(const QString &file) QList CutterCore::disassembleLines(RVA offset, int lines) { - CutterJson array = cmdj(QString("pdJ ") + QString::number(lines) + QString(" @ ") - + QString::number(offset)); - QList r; - - for (CutterJson object : array) { - DisassemblyLine line; - line.offset = object[RJsonKey::offset].toRVA(); - line.text = ansiEscapeToHtml(object[RJsonKey::text].toString()); - line.arrow = object[RJsonKey::arrow].toRVA(); - r << line; + CORE_LOCK(); + RzPVector *vec = rz_pvector_new(reinterpret_cast(rz_analysis_disasm_text_free)); + if (!vec) { + return {}; } + RzCoreDisasmOptions options = {}; + options.cbytes = 1; + options.vec = vec; + applyAtSeek( + [&]() { + if (rz_cons_singleton()->is_html) { + rz_cons_singleton()->is_html = false; + rz_cons_singleton()->was_html = true; + } + rz_core_print_disasm(core, offset, core->block, core->blocksize, lines, NULL, + &options); + }, + offset); + + QList r; + for (const auto &t : CutterPVector(vec)) { + DisassemblyLine line; + line.offset = t->offset; + line.text = ansiEscapeToHtml(t->text); + line.arrow = t->arrow; + r << line; + } + rz_pvector_free(vec); return r; } @@ -4354,7 +4401,7 @@ QString CutterCore::ansiEscapeToHtml(const QString &text) int len; char *html = rz_cons_html_filter(text.toUtf8().constData(), &len); if (!html) { - return QString(); + return {}; } QString r = QString::fromUtf8(html, len); rz_mem_free(html); diff --git a/src/core/Cutter.h b/src/core/Cutter.h index bdddd342..e544f3a5 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -81,6 +81,10 @@ public: RVA getOffset() const { return core_->offset; } /* Core functions (commands) */ + /* Almost the same as core_cmd_raw, + * only executes std::function instead of char* */ + QString getFunctionExecOut(const std::function &fcn, + const RVA addr = RVA_INVALID); static QString sanitizeStringForCommand(QString s); /** * @brief send a command to Rizin diff --git a/src/dialogs/XrefsDialog.cpp b/src/dialogs/XrefsDialog.cpp index 0a721e71..c7803562 100644 --- a/src/dialogs/XrefsDialog.cpp +++ b/src/dialogs/XrefsDialog.cpp @@ -131,8 +131,18 @@ void XrefsDialog::updatePreview(RVA addr) tempConfig.set("asm.lines", false); tempConfig.set("asm.bytes", false); - // Use cmd because cmRaw cannot handle the output properly. Why? - QString disas = Core()->cmd("pd--20 @ " + QString::number(addr)); + QString disas = Core()->getFunctionExecOut( + [](RzCore *core) { + ut64 offset = core->offset; + if (!rz_core_prevop_addr(core, core->offset, 20, &offset)) { + offset = rz_core_prevop_addr_force(core, core->offset, 20); + } + rz_core_seek(core, offset, true); + rz_core_print_disasm(core, core->offset, core->block, (int)core->blocksize, 40, + NULL, NULL); + return true; + }, + addr); ui->previewTextEdit->document()->setHtml(disas); // Does it make any sense? diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index f3f2dcfb..f08b8c5e 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -726,19 +726,23 @@ void DisassemblyContextMenu::on_actionNopInstruction_triggered() void DisassemblyContextMenu::showReverseJmpQuery() { - QString type; - - CutterJson array = Core()->cmdj("pdj 1 @ " + RzAddressString(offset)); - if (!array.size()) { + actionJmpReverse.setVisible(false); + RzCoreLocked core(Core()); + auto vec = reinterpret_cast(Core()->returnAtSeek( + [&]() { return rz_core_analysis_bytes(core, core->block, (int)core->blocksize, 1); }, + offset)); + if (!vec) { return; } - - type = array.first()["type"].toString(); - if (type == "cjmp") { - actionJmpReverse.setVisible(true); - } else { - actionJmpReverse.setVisible(false); + auto ab = reinterpret_cast(rz_pvector_head(vec)); + if (!(ab && ab->op)) { + rz_pvector_free(vec); + return; } + if (ab->op->type == RZ_ANALYSIS_OP_TYPE_CJMP) { + actionJmpReverse.setVisible(true); + } + rz_pvector_free(vec); } void DisassemblyContextMenu::on_actionJmpReverse_triggered() From 9e2201aa81656a2005a3609244b89ff1c90084e4 Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Sat, 6 Aug 2022 21:50:41 +0200 Subject: [PATCH 12/77] Mention OBS install mode for Linux (#3006) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4962b466..5642875b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Cutter is a free and open-source reverse engineering platform powered by [rizin] Cutter release binaries for all major platforms (Linux, macOS, Windows) can be downloaded from [GitHub Releases](https://github.com/rizinorg/cutter/releases). -- **Linux**: Download the `.AppImage` file. Then make it executable and run as below or use [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher). +- **Linux**: If your distribution provides it, check for `cutter` package in your package manager (or `cutter-re`). If not available there, we have setup repositories in [OBS](https://openbuildservice.org/) for some common distributions. Look at [https://software.opensuse.org/package/cutter-re](https://software.opensuse.org/download/package?package=cutter-re&project=home%3ARizinOrg) and follow the instructions there. Otherwise download the `.AppImage` file from our release, make it executable and run as below or use [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher). `chmod +x Cutter*.AppImage; ./Cutter*.AppImage` - **macOS**: Download the `.dmg` file or use [Homebrew Cask](https://github.com/Homebrew/homebrew-cask): From 35f9bfe135f5d87d8f0f47e2709b8dd952e42906 Mon Sep 17 00:00:00 2001 From: billow Date: Tue, 9 Aug 2022 09:29:08 +0800 Subject: [PATCH 13/77] Update rizin and version API (#3008) --- dist/bundle_python.ps1 | 2 +- rizin | 2 +- src/core/Cutter.cpp | 8 ++++---- src/core/Cutter.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dist/bundle_python.ps1 b/dist/bundle_python.ps1 index b421ed98..7e47c0d0 100644 --- a/dist/bundle_python.ps1 +++ b/dist/bundle_python.ps1 @@ -2,7 +2,7 @@ $arch = $args[0] $dist = $args[1] $py_version = (python --version).Split()[1] -$py_base = "python" + $py_version[0] + $py_version[2] +$py_base = "python" + $py_version.Split('.')[0] + $py_version.Split('.')[1] $py_platform = If ($arch -eq "x64") {"amd64"} Else {"win32"} $py_url = "https://www.python.org/ftp/python/${py_version}/python-${py_version}-embed-${py_platform}.zip" diff --git a/rizin b/rizin index b79201b4..215e4925 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit b79201b49a0d687c854b6e2f0c88fc8a7f2afa61 +Subproject commit 215e49253d3a35e1aae340b7ae8465018254ae87 diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 1cd9ad50..5c7b515f 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -4297,10 +4297,9 @@ void CutterCore::loadScript(const QString &scriptname) triggerRefreshAll(); } -QString CutterCore::getRizinVersionReadable() +QString CutterCore::getRizinVersionReadable(const char *program) { - return QString("%1 (%2)").arg(QString::fromUtf8(RZ_VERSION), - QString::fromUtf8(RZ_GITTIP).left(7)); + return fromOwnedCharPtr(rz_version_str(program)); } QString CutterCore::getVersionInformation() @@ -4337,7 +4336,8 @@ QString CutterCore::getVersionInformation() /* ... */ { NULL, NULL } }; - versionInfo.append(QString("%1 rz\n").arg(getRizinVersionReadable())); + versionInfo.append(getRizinVersionReadable()); + versionInfo.append("\n"); for (i = 0; vcs[i].name; i++) { struct vcs_t *v = &vcs[i]; const char *name = v->callback(); diff --git a/src/core/Cutter.h b/src/core/Cutter.h index e544f3a5..574d5258 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -201,7 +201,7 @@ public: { return asyncCmdEsil(command.toUtf8().constData(), task); } - QString getRizinVersionReadable(); + QString getRizinVersionReadable(const char *program = nullptr); QString getVersionInformation(); CutterJson parseJson(char *res, const char *cmd = nullptr); From a4a4b9d2ec2d88b9c4b1cabdbffb116ca912958b Mon Sep 17 00:00:00 2001 From: billow Date: Tue, 9 Aug 2022 23:51:20 +0800 Subject: [PATCH 14/77] Convert to rizin APIs `pc*` `px*` (#3007) --- src/core/Cutter.cpp | 22 ++++++++----- src/core/Cutter.h | 12 +++++++ src/core/MainWindow.cpp | 70 ++++++++++++++++++++++++++++------------- 3 files changed, 75 insertions(+), 29 deletions(-) diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 5c7b515f..78e82ec4 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -4246,28 +4246,36 @@ QList CutterCore::disassembleLines(RVA offset, int lines) */ QString CutterCore::hexdump(RVA address, int size, HexdumpFormats format) { - QString command = "px"; + CORE_LOCK(); + char *res = nullptr; switch (format) { case HexdumpFormats::Normal: + res = rz_core_print_hexdump_or_hexdiff_str(core, RZ_OUTPUT_MODE_STANDARD, address, size, + false); break; case HexdumpFormats::Half: - command += "h"; + res = rz_core_print_dump_str(core, RZ_OUTPUT_MODE_STANDARD, address, 2, size, + RZ_CORE_PRINT_FORMAT_TYPE_HEXADECIMAL); break; case HexdumpFormats::Word: - command += "w"; + res = rz_core_print_dump_str(core, RZ_OUTPUT_MODE_STANDARD, address, 4, size, + RZ_CORE_PRINT_FORMAT_TYPE_HEXADECIMAL); break; case HexdumpFormats::Quad: - command += "q"; + res = rz_core_print_dump_str(core, RZ_OUTPUT_MODE_STANDARD, address, 8, size, + RZ_CORE_PRINT_FORMAT_TYPE_HEXADECIMAL); break; case HexdumpFormats::Signed: - command += "d"; + res = rz_core_print_dump_str(core, RZ_OUTPUT_MODE_STANDARD, address, 1, size, + RZ_CORE_PRINT_FORMAT_TYPE_INTEGER); break; case HexdumpFormats::Octal: - command += "o"; + res = rz_core_print_dump_str(core, RZ_OUTPUT_MODE_STANDARD, address, 1, size, + RZ_CORE_PRINT_FORMAT_TYPE_OCTAL); break; } - return cmdRawAt(QString("%1 %2").arg(command).arg(size), address); + return fromOwnedCharPtr(res); } QByteArray CutterCore::hexStringToBytes(const QString &hex) diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 574d5258..dd042109 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -17,6 +17,7 @@ #include #include #include +#include class AsyncTaskManager; class BasicInstructionHighlighter; @@ -169,6 +170,17 @@ public: return ret; } + std::unique_ptr> seekTemp(RVA address) + { + auto seekBack = [&](const RVA *x) { + seekSilent(*x); + delete x; + }; + std::unique_ptr p { new RVA(getOffset()), seekBack }; + seekSilent(address); + return p; + } + CutterJson cmdj(const char *str); CutterJson cmdj(const QString &str) { return cmdj(str.toUtf8().constData()); } CutterJson cmdjAt(const char *str, RVA address); diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index e599809e..339884cc 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -149,7 +149,7 @@ void MainWindow::initUI() &MainWindow::addExtraDisassembly); connect(ui->actionExtraHexdump, &QAction::triggered, this, &MainWindow::addExtraHexdump); connect(ui->actionCommitChanges, &QAction::triggered, this, - [this]() { Core()->commitWriteCache(); }); + []() { Core()->commitWriteCache(); }); ui->actionCommitChanges->setEnabled(false); connect(Core(), &CutterCore::ioCacheChanged, ui->actionCommitChanges, &QAction::setEnabled); @@ -1692,35 +1692,55 @@ void MainWindow::on_actionImportPDB_triggered() } } +#define TYPE_BIG_ENDIAN(type, big_endian) big_endian ? type##_BE : type##_LE + void MainWindow::on_actionExport_as_code_triggered() { QStringList filters; - QMap cmdMap; + QMap typMap; + const bool big_endian = Core()->getConfigb("big_endian"); filters << tr("C uin8_t array (*.c)"); - cmdMap[filters.last()] = "pc"; + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_C_CPP_BYTES; filters << tr("C uin16_t array (*.c)"); - cmdMap[filters.last()] = "pch"; + typMap[filters.last()] = TYPE_BIG_ENDIAN(RZ_LANG_BYTE_ARRAY_C_CPP_HALFWORDS, big_endian); filters << tr("C uin32_t array (*.c)"); - cmdMap[filters.last()] = "pcw"; + typMap[filters.last()] = TYPE_BIG_ENDIAN(RZ_LANG_BYTE_ARRAY_C_CPP_WORDS, big_endian); filters << tr("C uin64_t array (*.c)"); - cmdMap[filters.last()] = "pcd"; - filters << tr("C string (*.c)"); - cmdMap[filters.last()] = "pcs"; - filters << tr("Shell-script that reconstructs the bin (*.sh)"); - cmdMap[filters.last()] = "pcS"; + typMap[filters.last()] = TYPE_BIG_ENDIAN(RZ_LANG_BYTE_ARRAY_C_CPP_DOUBLEWORDS, big_endian); + + filters << tr("Go array (*.go)"); + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_GOLANG; + filters << tr("Java array (*.java)"); + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_JAVA; filters << tr("JSON array (*.json)"); - cmdMap[filters.last()] = "pcj"; - filters << tr("JavaScript array (*.js)"); - cmdMap[filters.last()] = "pcJ"; + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_JSON; + filters << tr("Kotlin array (*.kt)"); + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_KOTLIN; + + filters << tr("Javascript array (*.js)"); + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_NODEJS; + filters << tr("ObjectiveC array (*.m)"); + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_OBJECTIVE_C; filters << tr("Python array (*.py)"); - cmdMap[filters.last()] = "pcp"; + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_PYTHON; + filters << tr("Rust array (*.rs)"); + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_RUST; + + filters << tr("Swift array (*.swift)"); + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_SWIFT; filters << tr("Print 'wx' Rizin commands (*.rz)"); - cmdMap[filters.last()] = "pc*"; + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_RIZIN; + filters << tr("Shell-script that reconstructs the bin (*.sh)"); + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_BASH; filters << tr("GAS .byte blob (*.asm, *.s)"); - cmdMap[filters.last()] = "pca"; - filters << tr(".bytes with instructions in comments (*.txt)"); - cmdMap[filters.last()] = "pcA"; + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_ASM; + + filters << tr("Yara (*.yar)"); + typMap[filters.last()] = RZ_LANG_BYTE_ARRAY_YARA; + /* special case */ + QString instructionsInComments = tr(".bytes with instructions in comments (*.txt)"); + filters << instructionsInComments; QFileDialog dialog(this, tr("Export as code")); dialog.setAcceptMode(QFileDialog::AcceptSave); @@ -1736,13 +1756,19 @@ void MainWindow::on_actionExport_as_code_triggered() qWarning() << "Can't open file"; return; } + TempConfig tempConfig; tempConfig.set("io.va", false); QTextStream fileOut(&file); - QString &cmd = cmdMap[dialog.selectedNameFilter()]; - - // Use cmd because cmdRaw would not handle such input - fileOut << Core()->cmd(cmd + " $s @ 0"); + auto ps = core->seekTemp(0); + auto rc = core->core(); + std::unique_ptr string { + dialog.selectedNameFilter() != instructionsInComments + ? rz_lang_byte_array(rc->block, rc->blocksize, typMap[dialog.selectedNameFilter()]) + : rz_core_print_bytes_with_inst(rc, rc->block, rc->offset, rc->blocksize), + free + }; + fileOut << string.get(); } void MainWindow::on_actionApplySigFromFile_triggered() From 278b4e19b906b242b9c1f31a804d8dfddbc480b1 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Wed, 10 Aug 2022 09:05:01 -0400 Subject: [PATCH 15/77] Update GraphGridLayout documentation (#3000) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated GraphGridLayout documentation * Don't use potentially misleading name "segment tree" . Not exactly segment tree (although sometimes called that), and for the purpose of high level understanding how graph layout works doesn't matter what it is. Any data structure which provides required queries could be used. Co-authored-by: Kārlis Seņko --- src/widgets/GraphGridLayout.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/widgets/GraphGridLayout.cpp b/src/widgets/GraphGridLayout.cpp index 0f636e2f..cf100e24 100644 --- a/src/widgets/GraphGridLayout.cpp +++ b/src/widgets/GraphGridLayout.cpp @@ -103,14 +103,24 @@ Edge routing can be split into: main column selection, rough routing, segment of Transition from source to target row is done using single vertical segment. This is called main column. +A sweep line is used for computing main columns: Blocks and edges are processed as events top to +bottom based off their row (max(start row, end row) for edges). Blocked columns are tracked in a +tree structure which allows searching nearest column with at least last N rows empty. The column +of the starting block is favored for the main column, otherwise the target block's column is chosen +if it is not blocked. If both the source and target columns are blocked, nearest unblocked column +is chosen. An empty column can always be found, in the worst case there are empty columns at the +sides of drawing. If two columns are equally close, the tie is broken based on whether the edge is a +true or false branch. In case of upward edges it is allowed to choose a column on the outside which +is slightly further than nearest empty to reduce the chance of producing tilted figure 8 shaped +crossing between two blocks. + Rough routing creates the path of edge using up to 5 segments using grid coordinates. Due to nodes being placed in a grid. Horizontal segments of edges can't intersect with any nodes. The path for edges is chosen so that it consists of at most 5 segments, typically resulting in sideways U shape or square Z shape. - short vertical segment from node to horizontal line - move to empty column -- vertical segment between starting row and end row, an empty column can always be found, in the -worst case there are empty columns at the sides of drawing +- vertical segment between starting row and end row - horizontal segment to target node column - short vertical segment connecting to target node @@ -118,9 +128,6 @@ There are 3 special cases: - source and target nodes are in the same column with no nodes between - single vertical segment - column bellow stating node is empty - segments 1-3 are merged - column above target node is empty - segments 3-5 are merged -Vertical segment intersection with nodes is prevented using a 2d array marking which vertical -segments are blocked and naively iterating through all rows between start and end at the desired -column. After rough routing segment offsets are calculated relative to their corresponding edge column. This ensures that two segments don't overlap. Segment offsets within each column are assigned greedily From ddefc0663ac2ec157c368bd68db10217877c75d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C4=81rlis=20Se=C5=86ko?= Date: Wed, 10 Aug 2022 14:01:44 +0300 Subject: [PATCH 16/77] Don't leak memory in bb highlighter. --- src/common/BasicBlockHighlighter.cpp | 21 ++++++--------------- src/common/BasicBlockHighlighter.h | 17 +++++++---------- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/common/BasicBlockHighlighter.cpp b/src/common/BasicBlockHighlighter.cpp index f81b336b..d8270d44 100644 --- a/src/common/BasicBlockHighlighter.cpp +++ b/src/common/BasicBlockHighlighter.cpp @@ -2,21 +2,14 @@ BasicBlockHighlighter::BasicBlockHighlighter() {} -BasicBlockHighlighter::~BasicBlockHighlighter() -{ - for (BasicBlockIt itr = bbMap.begin(); itr != bbMap.end(); ++itr) { - delete itr->second; - } -} - /** * @brief Highlight the basic block at address */ void BasicBlockHighlighter::highlight(RVA address, const QColor &color) { - BasicBlock *block = new BasicBlock; - block->address = address; - block->color = color; + BasicBlock block; + block.address = address; + block.color = color; bbMap[address] = block; } @@ -33,13 +26,11 @@ void BasicBlockHighlighter::clear(RVA address) * * If there is nothing to highlight at specified address, returns nullptr */ -BasicBlock *BasicBlockHighlighter::getBasicBlock(RVA address) +BasicBlockHighlighter::BasicBlock *BasicBlockHighlighter::getBasicBlock(RVA address) { - BasicBlockIt it; - - it = bbMap.find(address); + auto it = bbMap.find(address); if (it != bbMap.end()) { - return it->second; + return &it->second; } return nullptr; diff --git a/src/common/BasicBlockHighlighter.h b/src/common/BasicBlockHighlighter.h index fc84b32a..2e0825e3 100644 --- a/src/common/BasicBlockHighlighter.h +++ b/src/common/BasicBlockHighlighter.h @@ -6,26 +6,23 @@ class BasicBlockHighlighter; #include "Cutter.h" #include -struct BasicBlock -{ - RVA address; - QColor color; -}; - -typedef std::map::iterator BasicBlockIt; - class BasicBlockHighlighter { public: + struct BasicBlock + { + RVA address; + QColor color; + }; + BasicBlockHighlighter(); - ~BasicBlockHighlighter(); void highlight(RVA address, const QColor &color); void clear(RVA address); BasicBlock *getBasicBlock(RVA address); private: - std::map bbMap; + std::map bbMap; }; #endif // BASICBLOCKHIGHLIGHTER_H From c263d4bd1b35882a223f8cda7b3400fddbf92a80 Mon Sep 17 00:00:00 2001 From: billow Date: Thu, 11 Aug 2022 01:18:56 +0800 Subject: [PATCH 17/77] Convert `agcj` command call to the API (#3012) --- rizin | 2 +- src/widgets/CallGraph.cpp | 90 +++++++++++++++++++++++---------------- src/widgets/CallGraph.h | 1 - 3 files changed, 54 insertions(+), 39 deletions(-) diff --git a/rizin b/rizin index 215e4925..a9c59ce1 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit 215e49253d3a35e1aae340b7ae8465018254ae87 +Subproject commit a9c59ce1ddc44c498b6a9a02e36989a34f184286 diff --git a/src/widgets/CallGraph.cpp b/src/widgets/CallGraph.cpp index c66a862e..2fbf603d 100644 --- a/src/widgets/CallGraph.cpp +++ b/src/widgets/CallGraph.cpp @@ -53,11 +53,8 @@ void CallGraphView::showExportDialog() void CallGraphView::showAddress(RVA address) { if (global) { - auto addressMappingIt = addressMapping.find(address); - if (addressMappingIt != addressMapping.end()) { - selectBlockWithId(addressMappingIt->second); - showBlock(blocks[addressMappingIt->second]); - } + selectBlockWithId(address); + showBlock(blocks[address]); } else if (address != this->address) { this->address = address; refreshView(); @@ -72,53 +69,72 @@ void CallGraphView::refreshView() SimpleTextGraphView::refreshView(); } +static inline bool isBetween(ut64 a, ut64 x, ut64 b) +{ + return (a == UT64_MAX || a <= x) && (b == UT64_MAX || x <= b); +} + +using PRzList = std::unique_ptr; + void CallGraphView::loadCurrentGraph() { blockContent.clear(); blocks.clear(); - CutterJson nodes = Core()->cmdj(global ? "agCj" : QString("agcj @ %1").arg(address)); + const ut64 from = Core()->getConfigi("graph.from"); + const ut64 to = Core()->getConfigi("graph.to"); + const bool usenames = Core()->getConfigb("graph.json.usenames"); - QHash idMapping; + auto edges = std::unordered_set {}; + auto addFunction = [&](RzAnalysisFunction *fcn) { + GraphLayout::GraphBlock block; + block.entry = fcn->addr; - auto getId = [&](const QString &name) -> uint64_t { - auto nextId = idMapping.size(); - auto &itemId = idMapping[name]; - if (idMapping.size() != nextId) { - itemId = nextId; + auto xrefs = PRzList { rz_analysis_function_get_xrefs_from(fcn), rz_list_free }; + auto calls = std::unordered_set(); + for (const auto &xref : CutterRzList(xrefs.get())) { + const auto x = xref->to; + if (!(xref->type == RZ_ANALYSIS_XREF_TYPE_CALL && calls.find(x) == calls.end())) { + continue; + } + calls.insert(x); + block.edges.emplace_back(x); + edges.insert(x); } - return itemId; + + QString name = usenames ? fcn->name : RzAddressString(fcn->addr); + addBlock(std::move(block), name, fcn->addr); }; - for (CutterJson block : nodes) { - QString name = block["name"].toString(); - - auto edges = block["imports"]; - GraphLayout::GraphBlock layoutBlock; - layoutBlock.entry = getId(name); - for (auto edge : edges) { - auto targetName = edge.toString(); - auto targetId = getId(targetName); - layoutBlock.edges.emplace_back(targetId); + if (global) { + for (const auto &fcn : CutterRzList(Core()->core()->analysis->fcns)) { + if (!isBetween(from, fcn->addr, to)) { + continue; + } + addFunction(fcn); + } + } else { + const auto &fcn = Core()->functionIn(address); + if (fcn) { + addFunction(fcn); } - - // it would be good if address came directly from json instead of having to lookup by name - addBlock(std::move(layoutBlock), name, Core()->num(name)); } - for (auto it = idMapping.begin(), end = idMapping.end(); it != end; ++it) { - if (blocks.find(it.value()) == blocks.end()) { - GraphLayout::GraphBlock block; - block.entry = it.value(); - addBlock(std::move(block), it.key(), Core()->num(it.key())); + + for (const auto &x : edges) { + if (blockContent.find(x) != blockContent.end()) { + continue; } + GraphLayout::GraphBlock block; + block.entry = x; + QString flagName = Core()->flagAt(x); + QString name = usenames + ? (!flagName.isEmpty() ? flagName : QString("unk.%0").arg(RzAddressString(x))) + : RzAddressString(x); + addBlock(std::move(block), name, x); } if (blockContent.empty() && !global) { - addBlock({}, RzAddressString(address), address); - } - - addressMapping.clear(); - for (auto &it : blockContent) { - addressMapping[it.second.address] = it.first; + const auto name = RzAddressString(address); + addBlock({}, name, address); } computeGraphPlacement(); diff --git a/src/widgets/CallGraph.h b/src/widgets/CallGraph.h index 8085fdcf..2d348266 100644 --- a/src/widgets/CallGraph.h +++ b/src/widgets/CallGraph.h @@ -22,7 +22,6 @@ public: protected: bool global; ///< is this a global or function callgraph RVA address = RVA_INVALID; ///< function address if this is not a global callgraph - std::unordered_map addressMapping; ///< mapping from addresses to block id void loadCurrentGraph() override; void restoreCurrentBlock() override; From a5b0dd3ed204cbb24fa1b6b95e030fa89aa0fe53 Mon Sep 17 00:00:00 2001 From: billow Date: Thu, 11 Aug 2022 18:05:14 +0800 Subject: [PATCH 18/77] Convert commands(`agJ`, `anj`, `pi`) to rizin APIs (#3005) --- src/common/Configuration.cpp | 1 - src/common/TempConfig.h | 1 - src/core/Cutter.cpp | 66 +++++------- src/core/Cutter.h | 11 +- src/menus/DisassemblyContextMenu.cpp | 74 ++++++------- src/widgets/DisassemblerGraphView.cpp | 143 ++++++++++++++------------ 6 files changed, 140 insertions(+), 156 deletions(-) diff --git a/src/common/Configuration.cpp b/src/common/Configuration.cpp index 25d5907a..7fc76715 100644 --- a/src/common/Configuration.cpp +++ b/src/common/Configuration.cpp @@ -201,7 +201,6 @@ void Configuration::resetAll() { // Don't reset all rizin vars, that currently breaks a bunch of stuff. // settingsFile.remove()+loadInitials() should reset all settings configurable using Cutter GUI. - // Core()->cmdRaw("e-"); Core()->setSettings(); // Delete the file so no extra configuration is in it. diff --git a/src/common/TempConfig.h b/src/common/TempConfig.h index ba9b6f1e..a3c2239d 100644 --- a/src/common/TempConfig.h +++ b/src/common/TempConfig.h @@ -19,7 +19,6 @@ * { * TempConfig tempConfig; * tempConfig.set("asm.arch", "x86").set("asm.comments", false); - * return Core()->cmdRaw("pd"); * // config automatically restored at the end of scope * } * \endcode diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 78e82ec4..5e25c9dc 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -777,34 +777,32 @@ void CutterCore::delFlag(const QString &name) emit flagsChanged(); } +PRzAnalysisBytes CutterCore::getRzAnalysisBytesSingle(RVA addr) +{ + CORE_LOCK(); + ut8 buf[128]; + rz_io_read_at(core->io, addr, buf, sizeof(buf)); + std::unique_ptr vec { + returnAtSeek( + [&]() { return rz_core_analysis_bytes(core, buf, sizeof(buf), 1); }, addr), + rz_pvector_free + }; + auto ab = vec && rz_pvector_len(vec.get()) > 0 + ? reinterpret_cast(rz_pvector_pop_front(vec.get())) + : nullptr; + return { ab, rz_analysis_bytes_free }; +} + QString CutterCore::getInstructionBytes(RVA addr) { - auto ret = (char *)Core()->returnAtSeek( - [&]() { - CORE_LOCK(); - RzPVector *vec = rz_core_analysis_bytes(core, core->block, (int)core->blocksize, 1); - auto *ab = static_cast(rz_pvector_head(vec)); - char *str = strdup(ab->bytes); - rz_pvector_free(vec); - return str; - }, - addr); - return fromOwnedCharPtr(ret); + auto ab = getRzAnalysisBytesSingle(addr); + return ab ? ab->bytes : ""; } QString CutterCore::getInstructionOpcode(RVA addr) { - auto ret = (char *)Core()->returnAtSeek( - [&]() { - CORE_LOCK(); - RzPVector *vec = rz_core_analysis_bytes(core, core->block, (int)core->blocksize, 1); - auto *ab = static_cast(rz_pvector_head(vec)); - char *str = strdup(ab->opcode); - rz_pvector_free(vec); - return str; - }, - addr); - return fromOwnedCharPtr(ret); + auto ab = getRzAnalysisBytesSingle(addr); + return ab ? ab->opcode : ""; } void CutterCore::editInstruction(RVA addr, const QString &inst, bool fillWithNops) @@ -910,7 +908,7 @@ QString CutterCore::getString(RVA addr, uint64_t len, RzStrEnc encoding, bool es opt.length = len; opt.encoding = encoding; opt.escape_nl = escape_nl; - char *s = (char *)returnAtSeek([&]() { return rz_str_stringify_raw_buffer(&opt, NULL); }, addr); + char *s = returnAtSeek([&]() { return rz_str_stringify_raw_buffer(&opt, NULL); }, addr); return fromOwnedCharPtr(s); } @@ -1086,11 +1084,11 @@ RVA CutterCore::prevOpAddr(RVA startAddr, int count) RVA CutterCore::nextOpAddr(RVA startAddr, int count) { CORE_LOCK(); - auto vec = reinterpret_cast(returnAtSeek( + auto vec = returnAtSeek( [&]() { return rz_core_analysis_bytes(core, core->block, (int)core->blocksize, count + 1); }, - startAddr)); + startAddr); RVA addr = startAddr + 1; if (!vec) { return addr; @@ -1326,7 +1324,8 @@ QString CutterCore::disassemble(const QByteArray &data) QString CutterCore::disassembleSingleInstruction(RVA addr) { - return cmdRawAt("pi 1", addr).simplified(); + auto ab = getRzAnalysisBytesSingle(addr); + return QString(ab->disasm).simplified(); } RzAnalysisFunction *CutterCore::functionIn(ut64 addr) @@ -1430,19 +1429,8 @@ void CutterCore::createFunctionAt(RVA addr, QString name) RVA CutterCore::getOffsetJump(RVA addr) { - auto rva = (RVA *)Core()->returnAtSeek( - [&]() { - CORE_LOCK(); - RzPVector *vec = rz_core_analysis_bytes(core, core->block, (int)core->blocksize, 1); - auto *ab = static_cast(rz_pvector_head(vec)); - RVA *rva = new RVA(ab->op->jump); - rz_pvector_free(vec); - return rva; - }, - addr); - RVA ret = *rva; - delete rva; - return ret; + auto ab = getRzAnalysisBytesSingle(addr); + return ab && ab->op ? ab->op->jump : RVA_INVALID; } QList CutterCore::getDecompilers() diff --git a/src/core/Cutter.h b/src/core/Cutter.h index dd042109..b92167e3 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -32,6 +32,7 @@ class RizinTaskDialog; #include "common/Helpers.h" #include +#include #define Core() (CutterCore::instance()) @@ -60,6 +61,8 @@ struct CUTTER_EXPORT RegisterRef QString name; }; +using PRzAnalysisBytes = std::unique_ptr; + class CUTTER_EXPORT CutterCore : public QObject { Q_OBJECT @@ -153,7 +156,7 @@ public: return cmdRawAt(str.toUtf8().constData(), address); } - void applyAtSeek(std::function fn, RVA address) + void applyAtSeek(const std::function &fn, RVA address) { RVA oldOffset = getOffset(); seekSilent(address); @@ -161,11 +164,12 @@ public: seekSilent(oldOffset); } - void *returnAtSeek(std::function fn, RVA address) + template + T returnAtSeek(const std::function &fn, RVA address) { RVA oldOffset = getOffset(); seekSilent(address); - void *ret = fn(); + T ret = fn(); seekSilent(oldOffset); return ret; } @@ -273,6 +277,7 @@ public: void triggerFlagsChanged(); /* Edition functions */ + PRzAnalysisBytes getRzAnalysisBytesSingle(RVA addr); QString getInstructionBytes(RVA addr); QString getInstructionOpcode(RVA addr); void editInstruction(RVA addr, const QString &inst, bool fillWithNops = false); diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index f08b8c5e..ddbfea65 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -313,34 +313,34 @@ void DisassemblyContextMenu::addDebugMenu() QVector DisassemblyContextMenu::getThingUsedHere(RVA offset) { - QVector result; - const CutterJson array = Core()->cmdj("anj @ " + QString::number(offset)); - result.reserve(array.size()); - for (const auto &thing : array) { - auto obj = thing; - RVA offset = obj["offset"].toRVA(); - QString name; - - // If real names display is enabled, show flag's real name instead of full flag name - if (Config()->getConfigBool("asm.flags.real") && obj["realname"].valid()) { - name = obj["realname"].toString(); - } else { - name = obj["name"].toString(); - } - - QString typeString = obj["type"].toString(); - ThingUsedHere::Type type = ThingUsedHere::Type::Address; - if (typeString == "var") { - type = ThingUsedHere::Type::Var; - } else if (typeString == "flag") { - type = ThingUsedHere::Type::Flag; - } else if (typeString == "function") { - type = ThingUsedHere::Type::Function; - } else if (typeString == "address") { - type = ThingUsedHere::Type::Address; - } - result.push_back(ThingUsedHere { name, offset, type }); + RzCoreLocked core(Core()); + auto p = std::unique_ptr { + rz_core_analysis_name(core, offset), rz_core_analysis_name_free + }; + if (!p) { + return {}; } + + QVector result; + ThingUsedHere th; + th.offset = p->offset; + th.name = Config()->getConfigBool("asm.flags.real") && p->realname ? p->realname : p->name; + switch (p->type) { + case RZ_CORE_ANALYSIS_NAME_TYPE_FLAG: + th.type = ThingUsedHere::Type::Flag; + break; + case RZ_CORE_ANALYSIS_NAME_TYPE_FUNCTION: + th.type = ThingUsedHere::Type::Function; + break; + case RZ_CORE_ANALYSIS_NAME_TYPE_VAR: + th.type = ThingUsedHere::Type::Var; + break; + case RZ_CORE_ANALYSIS_NAME_TYPE_ADDRESS: + default: + th.type = ThingUsedHere::Type::Address; + break; + } + result.push_back(th); return result; } @@ -482,13 +482,7 @@ void DisassemblyContextMenu::setupRenaming() void DisassemblyContextMenu::aboutToShowSlot() { // check if set immediate base menu makes sense - RzPVector *vec = (RzPVector *)Core()->returnAtSeek( - [&]() { - RzCoreLocked core(Core()); - return rz_core_analysis_bytes(core, core->block, (int)core->blocksize, 1); - }, - offset); - auto *ab = static_cast(rz_pvector_head(vec)); + auto ab = Core()->getRzAnalysisBytesSingle(offset); bool immBase = ab && ab->op && (ab->op->val || ab->op->ptr); setBaseMenu->menuAction()->setVisible(immBase); @@ -514,7 +508,6 @@ void DisassemblyContextMenu::aboutToShowSlot() } } } - rz_pvector_free(vec); if (memBaseReg.isEmpty()) { // hide structure offset menu @@ -727,22 +720,13 @@ void DisassemblyContextMenu::on_actionNopInstruction_triggered() void DisassemblyContextMenu::showReverseJmpQuery() { actionJmpReverse.setVisible(false); - RzCoreLocked core(Core()); - auto vec = reinterpret_cast(Core()->returnAtSeek( - [&]() { return rz_core_analysis_bytes(core, core->block, (int)core->blocksize, 1); }, - offset)); - if (!vec) { - return; - } - auto ab = reinterpret_cast(rz_pvector_head(vec)); + auto ab = Core()->getRzAnalysisBytesSingle(offset); if (!(ab && ab->op)) { - rz_pvector_free(vec); return; } if (ab->op->type == RZ_ANALYSIS_OP_TYPE_CJMP) { actionJmpReverse.setVisible(true); } - rz_pvector_free(vec); } void DisassemblyContextMenu::on_actionJmpReverse_triggered() diff --git a/src/widgets/DisassemblerGraphView.cpp b/src/widgets/DisassemblerGraphView.cpp index 09dacdd2..63bf6832 100644 --- a/src/widgets/DisassemblerGraphView.cpp +++ b/src/widgets/DisassemblerGraphView.cpp @@ -109,14 +109,14 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se if (c.isValid()) { bbh->highlight(currBlockEntry, c); } - Config()->colorsUpdated(); + emit Config()->colorsUpdated(); }); actionUnhighlight.setText(tr("Unhighlight block")); connect(&actionUnhighlight, &QAction::triggered, this, [this]() { auto bbh = Core()->getBBHighlighter(); bbh->clear(blockForAddress(this->seekable->getOffset())->entry); - Config()->colorsUpdated(); + emit Config()->colorsUpdated(); }); QAction *highlightBI = new QAction(this); @@ -162,9 +162,8 @@ void DisassemblerGraphView::connectSeekChanged(bool disconn) DisassemblerGraphView::~DisassemblerGraphView() { - for (QShortcut *shortcut : shortcuts) { - delete shortcut; - } + qDeleteAll(shortcuts); + shortcuts.clear(); } void DisassemblerGraphView::refreshView() @@ -182,13 +181,6 @@ void DisassemblerGraphView::loadCurrentGraph() .set("asm.lines", false) .set("asm.lines.fcn", false); - CutterJson functions; - RzAnalysisFunction *fcn = Core()->functionIn(seekable->getOffset()); - if (fcn) { - currentFcnAddr = fcn->addr; - functions = Core()->cmdj("agJ " + RzAddressString(fcn->addr)); - } - disassembly_blocks.clear(); blocks.clear(); @@ -197,7 +189,20 @@ void DisassemblerGraphView::loadCurrentGraph() highlight_token = nullptr; } - emptyGraph = !functions.size(); + RzAnalysisFunction *fcn = Core()->functionIn(seekable->getOffset()); + + windowTitle = tr("Graph"); + if (fcn && RZ_STR_ISNOTEMPTY(fcn->name)) { + std::unique_ptr fcnName { + rz_str_escape_utf8_for_json(fcn->name, -1), std::free + }; + windowTitle += QString("(%0)").arg(fcnName.get()); + } else { + windowTitle += "(Empty)"; + } + emit nameChanged(windowTitle); + + emptyGraph = !fcn; if (emptyGraph) { // If there's no function to print, just add a message if (!emptyText) { @@ -213,31 +218,20 @@ void DisassemblerGraphView::loadCurrentGraph() } // Refresh global "empty graph" variable so other widget know there is nothing to show here Core()->setGraphEmpty(emptyGraph); + setEntry(fcn ? fcn->addr : RVA_INVALID); - CutterJson func = functions.first(); - - windowTitle = tr("Graph"); - QString funcName = func["name"].toString().trimmed(); - if (emptyGraph) { - windowTitle += " (Empty)"; - } else if (!funcName.isEmpty()) { - windowTitle += " (" + funcName + ")"; + if (!fcn) { + return; } - emit nameChanged(windowTitle); - RVA entry = func["offset"].toRVA(); - - setEntry(entry); - for (CutterJson block : func["blocks"]) { - RVA block_entry = block["offset"].toRVA(); - RVA block_size = block["size"].toRVA(); - RVA block_fail = block["fail"].toRVA(); - RVA block_jump = block["jump"].toRVA(); + for (const auto &bbi : CutterRzList(fcn->bbs)) { + RVA bbiFail = bbi->fail; + RVA bbiJump = bbi->jump; DisassemblyBlock db; GraphBlock gb; - gb.entry = block_entry; - db.entry = block_entry; + gb.entry = bbi->addr; + db.entry = bbi->addr; if (Config()->getGraphBlockEntryOffset()) { // QColor(0,0,0,0) is transparent db.header_text = Text("[" + RzAddressString(db.entry) + "]", ConfigColor("offset"), @@ -245,50 +239,69 @@ void DisassemblerGraphView::loadCurrentGraph() } db.true_path = RVA_INVALID; db.false_path = RVA_INVALID; - if (block_fail) { - db.false_path = block_fail; - gb.edges.emplace_back(block_fail); + if (bbiFail) { + db.false_path = bbiFail; + gb.edges.emplace_back(bbiFail); } - if (block_jump) { - if (block_fail) { - db.true_path = block_jump; + if (bbiJump) { + if (bbiFail) { + db.true_path = bbiJump; } - gb.edges.emplace_back(block_jump); + gb.edges.emplace_back(bbiJump); } - CutterJson switchOp = block["switchop"]; - if (switchOp.size()) { - for (CutterJson caseOp : switchOp["cases"]) { - RVA caseJump = caseOp["jump"].toRVA(); - if (caseJump == RVA_INVALID) { + RzAnalysisSwitchOp *switchOp = bbi->switch_op; + if (switchOp) { + for (const auto &caseOp : CutterRzList(switchOp->cases)) { + if (caseOp->jump == RVA_INVALID) { continue; } - gb.edges.emplace_back(caseJump); + gb.edges.emplace_back(caseOp->jump); } } - CutterJson opArray = block["ops"]; - CutterJson::iterator iterator = opArray.begin(); - while (iterator != opArray.end()) { - CutterJson op = *iterator; - Instr i; - i.addr = op["offset"].toUt64(); + RzCoreLocked core(Core()); + std::unique_ptr buf { new ut8[bbi->size] }; + if (!buf) { + break; + } + rz_io_read_at(core->io, bbi->addr, buf.get(), (int)bbi->size); - ++iterator; + std::unique_ptr vec { + rz_pvector_new(reinterpret_cast(rz_analysis_disasm_text_free)), + rz_pvector_free + }; + if (!vec) { + break; + } - if (iterator != opArray.end()) { + RzCoreDisasmOptions options = {}; + options.vec = vec.get(); + options.cbytes = 1; + rz_core_print_disasm(core, bbi->addr, buf.get(), (int)bbi->size, (int)bbi->size, NULL, + &options); + + auto vecVisitor = CutterPVector(vec.get()); + auto iter = vecVisitor.begin(); + while (iter != vecVisitor.end()) { + RzAnalysisDisasmText *op = *iter; + Instr instr; + instr.addr = op->offset; + + ++iter; + if (iter != vecVisitor.end()) { // get instruction size from distance to next instruction ... - RVA nextOffset = (*iterator)["offset"].toRVA(); - i.size = nextOffset - i.addr; + RVA nextOffset = (*iter)->offset; + instr.size = nextOffset - instr.addr; } else { // or to the end of the block. - i.size = (block_entry + block_size) - i.addr; + instr.size = (bbi->addr + bbi->size) - instr.addr; } QTextDocument textDoc; - textDoc.setHtml(CutterCore::ansiEscapeToHtml(op["text"].toString())); + textDoc.setHtml(CutterCore::ansiEscapeToHtml(op->text)); - i.plainText = textDoc.toPlainText(); + instr.plainText = textDoc.toPlainText(); RichTextPainter::List richText = RichTextPainter::fromTextDocument(textDoc); // Colors::colorizeAssembly(richText, textDoc.toPlainText(), 0); @@ -296,23 +309,19 @@ void DisassemblerGraphView::loadCurrentGraph() bool cropped; int blockLength = Config()->getGraphBlockMaxChars() + Core()->getConfigb("asm.bytes") * 24 + Core()->getConfigb("asm.emu") * 10; - i.text = Text(RichTextPainter::cropped(richText, blockLength, "...", &cropped)); + instr.text = Text(RichTextPainter::cropped(richText, blockLength, "...", &cropped)); if (cropped) - i.fullText = richText; + instr.fullText = richText; else - i.fullText = Text(); - db.instrs.push_back(i); + instr.fullText = Text(); + db.instrs.push_back(instr); } disassembly_blocks[db.entry] = db; prepareGraphNode(gb); - addBlock(gb); } cleanupEdges(blocks); - - if (func["blocks"].size()) { - computeGraphPlacement(); - } + computeGraphPlacement(); } DisassemblerGraphView::EdgeConfigurationMapping DisassemblerGraphView::getEdgeConfigurations() From 6b7b1d1450d0d47a8dd165005b5e4f872e04cb35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C4=81rlis=20Se=C5=86ko?= Date: Thu, 11 Aug 2022 10:19:18 +0300 Subject: [PATCH 19/77] Do not check "Built from source" in bug report template by default. --- src/common/BugReporting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/BugReporting.cpp b/src/common/BugReporting.cpp index 18aa36ef..95da855f 100644 --- a/src/common/BugReporting.cpp +++ b/src/common/BugReporting.cpp @@ -23,7 +23,7 @@ void openIssue() url = "https://github.com/rizinorg/cutter/issues/new?&body=**Environment information**\n* " "Operating System: " + osInfo + "\n* Cutter version: " + CUTTER_VERSION_FULL + "\n* Obtained from:\n" - + " - [x] Built from source\n - [ ] Downloaded release from Cutter website or GitHub " + + " - [ ] Built from source\n - [ ] Downloaded release from Cutter website or GitHub " "\n" " - [ ] Distribution repository\n* File format: " + format + "\n * Arch: " + arch + "\n * Type: " + type From 617c79f97653c52f7d4d36fcb4e5f0a86c94723c Mon Sep 17 00:00:00 2001 From: billow Date: Thu, 11 Aug 2022 20:46:18 +0800 Subject: [PATCH 20/77] Convert `pdsf` to API (#3010) * convert `pdsf` * remove unused - `CutterCore.opcodes` - `cmdList` --- src/common/Highlighter.cpp | 9 --------- src/core/Cutter.cpp | 4 +--- src/core/Cutter.h | 8 +------- src/core/MainWindow.cpp | 12 +++++++++--- src/widgets/FunctionsWidget.cpp | 16 +++++++++++++--- 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/common/Highlighter.cpp b/src/common/Highlighter.cpp index 5431a398..03a9e3bf 100644 --- a/src/common/Highlighter.cpp +++ b/src/common/Highlighter.cpp @@ -9,15 +9,6 @@ Highlighter::Highlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) core = Core(); - keywordFormat.setForeground(QColor(65, 131, 215)); - - for (const QString &pattern : this->core->opcodes) { - rule.pattern.setPattern("\\b" + pattern + "\\b"); - rule.pattern.setPatternOptions(QRegularExpression::CaseInsensitiveOption); - rule.format = keywordFormat; - highlightingRules.append(rule); - } - regFormat.setForeground(QColor(236, 100, 75)); for (const QString &pattern : this->core->regs) { diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 5e25c9dc..c69c4df5 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -2944,11 +2944,9 @@ bool CutterCore::isGraphEmpty() return emptyGraph; } -void CutterCore::getOpcodes() +void CutterCore::getRegs() { CORE_LOCK(); - this->opcodes = cmdList("?O"); - this->regs = {}; const RzList *rs = rz_reg_get_list(getReg(), RZ_REG_TYPE_ANY); if (!rs) { diff --git a/src/core/Cutter.h b/src/core/Cutter.h index b92167e3..01bcc24d 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -188,11 +188,6 @@ public: CutterJson cmdj(const char *str); CutterJson cmdj(const QString &str) { return cmdj(str.toUtf8().constData()); } CutterJson cmdjAt(const char *str, RVA address); - QStringList cmdList(const char *str) - { - return cmd(str).split(QLatin1Char('\n'), CUTTER_QT_SKIP_EMPTY_PARTS); - } - QStringList cmdList(const QString &str) { return cmdList(str.toUtf8().constData()); } QString cmdTask(const QString &str); CutterJson cmdjTask(const QString &str); /** @@ -595,8 +590,7 @@ public: void setGraphEmpty(bool empty); bool isGraphEmpty(); - void getOpcodes(); - QList opcodes; + void getRegs(); QList regs; void setSettings(); diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 339884cc..bfd3434d 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -637,7 +637,7 @@ bool MainWindow::openProject(const QString &file) void MainWindow::finalizeOpen() { - core->getOpcodes(); + core->getRegs(); core->updateSeek(); refreshAll(); // Add fortune message @@ -1762,10 +1762,16 @@ void MainWindow::on_actionExport_as_code_triggered() QTextStream fileOut(&file); auto ps = core->seekTemp(0); auto rc = core->core(); + const auto size = static_cast(rz_io_fd_size(rc->io, rc->file->fd)); + auto buffer = std::vector(size); + if (!rz_io_read_at(Core()->core()->io, 0, buffer.data(), size)) { + return; + } + std::unique_ptr string { dialog.selectedNameFilter() != instructionsInComments - ? rz_lang_byte_array(rc->block, rc->blocksize, typMap[dialog.selectedNameFilter()]) - : rz_core_print_bytes_with_inst(rc, rc->block, rc->offset, rc->blocksize), + ? rz_lang_byte_array(buffer.data(), size, typMap[dialog.selectedNameFilter()]) + : rz_core_print_bytes_with_inst(rc, buffer.data(), 0, size), free }; fileOut << string.get(); diff --git a/src/widgets/FunctionsWidget.cpp b/src/widgets/FunctionsWidget.cpp index b59cfb34..864780c4 100644 --- a/src/widgets/FunctionsWidget.cpp +++ b/src/widgets/FunctionsWidget.cpp @@ -231,7 +231,17 @@ QVariant FunctionModel::data(const QModelIndex &index, int role) const QStringList disasmPreview = Core()->getDisassemblyPreview(function.offset, kMaxTooltipDisasmPreviewLines); - const QStringList &summary = Core()->cmdList(QString("pdsf @ %1").arg(function.offset)); + QStringList summary {}; + { + auto seeker = Core()->seekTemp(function.offset); + auto strings = std::unique_ptr { + rz_core_print_disasm_strings(Core()->core(), RZ_CORE_DISASM_STRINGS_MODE_FUNCTION, + 0, NULL), + free + }; + summary = QString(strings.get()).split('\n', CUTTER_QT_SKIP_EMPTY_PARTS); + } + const QFont &fnt = Config()->getFont(); QFontMetrics fm { fnt }; @@ -245,7 +255,7 @@ QVariant FunctionModel::data(const QModelIndex &index, int role) const } } if (disasmPreview.isEmpty() && highlights.isEmpty()) - return QVariant(); + return {}; QString toolTipContent = QString("
contains(function.offset); default: - return QVariant(); + return {}; } } From 39bf5c6429d199a9a18e16cfb1cbd7e285ca79c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C4=81rlis=20Se=C5=86ko?= Date: Thu, 11 Aug 2022 11:01:56 +0300 Subject: [PATCH 21/77] Don't try to load files with wrong extension as native plugins. #2626 --- src/plugins/PluginManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugins/PluginManager.cpp b/src/plugins/PluginManager.cpp index ac9c6104..6f6a5aa5 100644 --- a/src/plugins/PluginManager.cpp +++ b/src/plugins/PluginManager.cpp @@ -133,6 +133,10 @@ QString PluginManager::getUserPluginsDirectory() const void PluginManager::loadNativePlugins(const QDir &directory) { for (const QString &fileName : directory.entryList(QDir::Files)) { + if (!QLibrary::isLibrary(fileName)) { + // Reduce amount of warnings, by not attempting files which are obviously not plugins + continue; + } QPluginLoader pluginLoader(directory.absoluteFilePath(fileName)); QObject *plugin = pluginLoader.instance(); if (!plugin) { From 41c4857ed9c1fb531b86fa8d66f6bb9c8398f6b1 Mon Sep 17 00:00:00 2001 From: karliss Date: Sat, 13 Aug 2022 23:12:57 +0300 Subject: [PATCH 22/77] Introduce some helpers to deal with rizin C API more cleanly. (#3020) --- src/CMakeLists.txt | 2 + src/common/DecompilerHighlighter.cpp | 3 +- src/core/Cutter.cpp | 80 ++++++------- src/core/Cutter.h | 47 ++++---- src/core/CutterCommon.h | 98 +--------------- src/core/MainWindow.cpp | 8 +- src/core/RizinCpp.cpp | 2 + src/core/RizinCpp.h | 154 ++++++++++++++++++++++++++ src/dialogs/EditMethodDialog.cpp | 6 +- src/menus/DisassemblyContextMenu.cpp | 5 +- src/widgets/CallGraph.cpp | 3 +- src/widgets/DisassemblerGraphView.cpp | 10 +- src/widgets/FunctionsWidget.cpp | 8 +- src/widgets/VisualNavbar.cpp | 8 +- src/widgets/VisualNavbar.h | 2 +- 15 files changed, 238 insertions(+), 198 deletions(-) create mode 100644 src/core/RizinCpp.cpp create mode 100644 src/core/RizinCpp.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba6691c9..9b606132 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES Main.cpp core/Cutter.cpp core/CutterJson.cpp + core/RizinCpp.cpp dialogs/EditStringDialog.cpp dialogs/WriteCommandsDialogs.cpp widgets/DisassemblerGraphView.cpp @@ -154,6 +155,7 @@ set(HEADER_FILES core/CutterCommon.h core/CutterDescriptions.h core/CutterJson.h + core/RizinCpp.h dialogs/EditStringDialog.h dialogs/WriteCommandsDialogs.h widgets/DisassemblerGraphView.h diff --git a/src/common/DecompilerHighlighter.cpp b/src/common/DecompilerHighlighter.cpp index 48183cc2..af7cb48f 100644 --- a/src/common/DecompilerHighlighter.cpp +++ b/src/common/DecompilerHighlighter.cpp @@ -49,8 +49,7 @@ void DecompilerHighlighter::highlightBlock(const QString &) size_t start = block.position(); size_t end = block.position() + block.length(); - std::unique_ptr annotations( - rz_annotated_code_annotations_range(code, start, end), &rz_pvector_free); + auto annotations = fromOwned(rz_annotated_code_annotations_range(code, start, end)); void **iter; rz_pvector_foreach(annotations.get(), iter) { diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index c69c4df5..ac8634b1 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -120,13 +120,6 @@ static void updateOwnedCharPtr(char *&variable, const QString &newValue) variable = strdup(data.data()); } -static QString fromOwnedCharPtr(char *str) -{ - QString result(str ? str : ""); - rz_mem_free(str); - return result; -} - static bool reg_sync(RzCore *core, RzRegisterType type, bool write) { if (rz_core_is_debug(core)) { @@ -782,11 +775,10 @@ PRzAnalysisBytes CutterCore::getRzAnalysisBytesSingle(RVA addr) CORE_LOCK(); ut8 buf[128]; rz_io_read_at(core->io, addr, buf, sizeof(buf)); - std::unique_ptr vec { - returnAtSeek( - [&]() { return rz_core_analysis_bytes(core, buf, sizeof(buf), 1); }, addr), - rz_pvector_free - }; + + auto seek = seekTemp(addr); + auto vec = fromOwned(rz_core_analysis_bytes(core, buf, sizeof(buf), 1)); + auto ab = vec && rz_pvector_len(vec.get()) > 0 ? reinterpret_cast(rz_pvector_pop_front(vec.get())) : nullptr; @@ -819,14 +811,20 @@ void CutterCore::editInstruction(RVA addr, const QString &inst, bool fillWithNop void CutterCore::nopInstruction(RVA addr) { CORE_LOCK(); - applyAtSeek([&]() { rz_core_hack(core, "nop"); }, addr); + { + auto seek = seekTemp(addr); + rz_core_hack(core, "nop"); + } emit instructionChanged(addr); } void CutterCore::jmpReverse(RVA addr) { CORE_LOCK(); - applyAtSeek([&]() { rz_core_hack(core, "recj"); }, addr); + { + auto seek = seekTemp(addr); + rz_core_hack(core, "recj"); + } emit instructionChanged(addr); } @@ -908,8 +906,8 @@ QString CutterCore::getString(RVA addr, uint64_t len, RzStrEnc encoding, bool es opt.length = len; opt.encoding = encoding; opt.escape_nl = escape_nl; - char *s = returnAtSeek([&]() { return rz_str_stringify_raw_buffer(&opt, NULL); }, addr); - return fromOwnedCharPtr(s); + auto seek = seekTemp(addr); + return fromOwnedCharPtr(rz_str_stringify_raw_buffer(&opt, NULL)); } QString CutterCore::getMetaString(RVA addr) @@ -993,12 +991,11 @@ void CutterCore::applyStructureOffset(const QString &structureOffset, RVA offset offset = getOffset(); } - applyAtSeek( - [&]() { - CORE_LOCK(); - rz_core_analysis_hint_set_offset(core, structureOffset.toUtf8().constData()); - }, - offset); + { + CORE_LOCK(); + auto seek = seekTemp(offset); + rz_core_analysis_hint_set_offset(core, structureOffset.toUtf8().constData()); + } emit instructionChanged(offset); } @@ -1084,22 +1081,19 @@ RVA CutterCore::prevOpAddr(RVA startAddr, int count) RVA CutterCore::nextOpAddr(RVA startAddr, int count) { CORE_LOCK(); - auto vec = returnAtSeek( - [&]() { - return rz_core_analysis_bytes(core, core->block, (int)core->blocksize, count + 1); - }, - startAddr); + auto seek = seekTemp(startAddr); + auto vec = + fromOwned(rz_core_analysis_bytes(core, core->block, (int)core->blocksize, count + 1)); + RVA addr = startAddr + 1; if (!vec) { return addr; } - auto ab = reinterpret_cast(rz_pvector_tail(vec)); + auto ab = reinterpret_cast(rz_pvector_tail(vec.get())); if (!(ab && ab->op)) { - rz_pvector_free(vec); return addr; } addr = ab->op->addr; - rz_pvector_free(vec); return addr; } @@ -4193,34 +4187,32 @@ void CutterCore::loadPDB(const QString &file) QList CutterCore::disassembleLines(RVA offset, int lines) { CORE_LOCK(); - RzPVector *vec = rz_pvector_new(reinterpret_cast(rz_analysis_disasm_text_free)); + auto vec = fromOwned( + rz_pvector_new(reinterpret_cast(rz_analysis_disasm_text_free))); if (!vec) { return {}; } RzCoreDisasmOptions options = {}; options.cbytes = 1; - options.vec = vec; - applyAtSeek( - [&]() { - if (rz_cons_singleton()->is_html) { - rz_cons_singleton()->is_html = false; - rz_cons_singleton()->was_html = true; - } - rz_core_print_disasm(core, offset, core->block, core->blocksize, lines, NULL, - &options); - }, - offset); + options.vec = vec.get(); + { + auto restoreSeek = seekTemp(offset); + if (rz_cons_singleton()->is_html) { + rz_cons_singleton()->is_html = false; + rz_cons_singleton()->was_html = true; + } + rz_core_print_disasm(core, offset, core->block, core->blocksize, lines, NULL, &options); + } QList r; - for (const auto &t : CutterPVector(vec)) { + for (const auto &t : CutterPVector(vec.get())) { DisassemblyLine line; line.offset = t->offset; line.text = ansiEscapeToHtml(t->text); line.arrow = t->arrow; r << line; } - rz_pvector_free(vec); return r; } diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 01bcc24d..c9b92636 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -156,33 +156,34 @@ public: return cmdRawAt(str.toUtf8().constData(), address); } - void applyAtSeek(const std::function &fn, RVA address) + class SeekReturn { - RVA oldOffset = getOffset(); - seekSilent(address); - fn(); - seekSilent(oldOffset); - } + RVA returnAddress; + bool empty = true; - template - T returnAtSeek(const std::function &fn, RVA address) - { - RVA oldOffset = getOffset(); - seekSilent(address); - T ret = fn(); - seekSilent(oldOffset); - return ret; - } - - std::unique_ptr> seekTemp(RVA address) - { - auto seekBack = [&](const RVA *x) { - seekSilent(*x); - delete x; + public: + SeekReturn(RVA returnAddress) : returnAddress(returnAddress), empty(false) {} + ~SeekReturn() + { + if (!empty) { + Core()->seekSilent(returnAddress); + } + } + SeekReturn(SeekReturn &&from) + { + if (this != &from) { + returnAddress = from.returnAddress; + empty = from.empty; + from.empty = true; + } }; - std::unique_ptr p { new RVA(getOffset()), seekBack }; + }; + + SeekReturn seekTemp(RVA address) + { + SeekReturn returner(getOffset()); seekSilent(address); - return p; + return returner; } CutterJson cmdj(const char *str); diff --git a/src/core/CutterCommon.h b/src/core/CutterCommon.h index 4adbf234..a118cd00 100644 --- a/src/core/CutterCommon.h +++ b/src/core/CutterCommon.h @@ -7,6 +7,7 @@ #include "rz_core.h" #include +#include "RizinCpp.h" // Workaround for compile errors on Windows #ifdef Q_OS_WIN @@ -14,103 +15,6 @@ # undef max #endif // Q_OS_WIN -// Rizin list iteration macros -#define CutterRzListForeach(list, it, type, x) \ - if (list) \ - for (it = list->head; it && ((x = static_cast(it->data))); it = it->n) - -#define CutterRzVectorForeach(vec, it, type) \ - if ((vec) && (vec)->a) \ - for (it = (type *)(vec)->a; \ - (char *)it != (char *)(vec)->a + ((vec)->len * (vec)->elem_size); \ - it = (type *)((char *)it + (vec)->elem_size)) - -template -class CutterPVector -{ -private: - const RzPVector *const vec; - -public: - class iterator : public std::iterator - { - private: - T **p; - - public: - iterator(T **p) : p(p) {} - iterator(const iterator &o) : p(o.p) {} - iterator &operator++() - { - p++; - return *this; - } - iterator operator++(int) - { - iterator tmp(*this); - operator++(); - return tmp; - } - bool operator==(const iterator &rhs) const { return p == rhs.p; } - bool operator!=(const iterator &rhs) const { return p != rhs.p; } - T *operator*() { return *p; } - }; - - CutterPVector(const RzPVector *vec) : vec(vec) {} - iterator begin() const { return iterator(reinterpret_cast(vec->v.a)); } - iterator end() const { return iterator(reinterpret_cast(vec->v.a) + vec->v.len); } -}; - -template -class CutterRzList -{ -private: - const RzList *const list; - -public: - class iterator : public std::iterator - { - private: - RzListIter *iter; - - public: - explicit iterator(RzListIter *iter) : iter(iter) {} - iterator(const iterator &o) : iter(o.iter) {} - iterator &operator++() - { - if (!iter) { - return *this; - } - iter = iter->n; - return *this; - } - iterator operator++(int) - { - iterator tmp(*this); - operator++(); - return tmp; - } - bool operator==(const iterator &rhs) const { return iter == rhs.iter; } - bool operator!=(const iterator &rhs) const { return iter != rhs.iter; } - T *operator*() - { - if (!iter) { - return nullptr; - } - return reinterpret_cast(iter->data); - } - }; - - explicit CutterRzList(const RzList *l) : list(l) {} - iterator begin() const - { - if (!list) { - return iterator(nullptr); - } - return iterator(list->head); - } - iterator end() const { return iterator(nullptr); } -}; // Global information for Cutter #define APPNAME "Cutter" diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index bfd3434d..6a840fcc 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -1768,12 +1768,12 @@ void MainWindow::on_actionExport_as_code_triggered() return; } - std::unique_ptr string { + + + auto string = fromOwned( dialog.selectedNameFilter() != instructionsInComments ? rz_lang_byte_array(buffer.data(), size, typMap[dialog.selectedNameFilter()]) - : rz_core_print_bytes_with_inst(rc, buffer.data(), 0, size), - free - }; + : rz_core_print_bytes_with_inst(rc, buffer.data(), 0, size)); fileOut << string.get(); } diff --git a/src/core/RizinCpp.cpp b/src/core/RizinCpp.cpp new file mode 100644 index 00000000..7418a3f9 --- /dev/null +++ b/src/core/RizinCpp.cpp @@ -0,0 +1,2 @@ +#include "RizinCpp.h" + diff --git a/src/core/RizinCpp.h b/src/core/RizinCpp.h new file mode 100644 index 00000000..ae4fcb03 --- /dev/null +++ b/src/core/RizinCpp.h @@ -0,0 +1,154 @@ +/** \file RizinCpp.h + * Various utilities for easier and safer interactions with Rizin + * from C++ code. + */ +#ifndef RIZINCPP_H +#define RIZINCPP_H + +#include "rz_core.h" +#include +#include + +static inline QString fromOwnedCharPtr(char *str) +{ + QString result(str ? str : ""); + rz_mem_free(str); + return result; +} + +template +std::unique_ptr fromOwned(T *data, F *freeFunction) +{ + return std::unique_ptr { data, freeFunction }; +} + +static inline std::unique_ptr fromOwned(char *text) +{ + return { text, rz_mem_free }; +} + +template +class FreeBinder +{ +public: + void operator()(T *data) { func(data); } +}; + +template +using UniquePtrC = std::unique_ptr>; + +template +using UniquePtrCP = UniquePtrC::type, func>; + +static inline auto fromOwned(RZ_OWN RzPVector *data) + -> UniquePtrCP +{ + return { data, {} }; +} + +static inline auto fromOwned(RZ_OWN RzList *data) + -> UniquePtrCP +{ + return { data, {} }; +} + +// Rizin list iteration macros +// deprecated, prefer using CutterPVector and CutterRzList instead +#define CutterRzListForeach(list, it, type, x) \ + if (list) \ + for (it = list->head; it && ((x = static_cast(it->data))); it = it->n) + +#define CutterRzVectorForeach(vec, it, type) \ + if ((vec) && (vec)->a) \ + for (it = (type *)(vec)->a; \ + (char *)it != (char *)(vec)->a + ((vec)->len * (vec)->elem_size); \ + it = (type *)((char *)it + (vec)->elem_size)) + +template +class CutterPVector +{ +private: + const RzPVector *const vec; + +public: + class iterator : public std::iterator + { + private: + T **p; + + public: + iterator(T **p) : p(p) {} + iterator(const iterator &o) : p(o.p) {} + iterator &operator++() + { + p++; + return *this; + } + iterator operator++(int) + { + iterator tmp(*this); + operator++(); + return tmp; + } + bool operator==(const iterator &rhs) const { return p == rhs.p; } + bool operator!=(const iterator &rhs) const { return p != rhs.p; } + T *operator*() { return *p; } + }; + + CutterPVector(const RzPVector *vec) : vec(vec) {} + iterator begin() const { return iterator(reinterpret_cast(vec->v.a)); } + iterator end() const { return iterator(reinterpret_cast(vec->v.a) + vec->v.len); } +}; + +template +class CutterRzList +{ +private: + const RzList *const list; + +public: + class iterator : public std::iterator + { + private: + RzListIter *iter; + + public: + explicit iterator(RzListIter *iter) : iter(iter) {} + iterator(const iterator &o) : iter(o.iter) {} + iterator &operator++() + { + if (!iter) { + return *this; + } + iter = iter->n; + return *this; + } + iterator operator++(int) + { + iterator tmp(*this); + operator++(); + return tmp; + } + bool operator==(const iterator &rhs) const { return iter == rhs.iter; } + bool operator!=(const iterator &rhs) const { return iter != rhs.iter; } + T *operator*() + { + if (!iter) { + return nullptr; + } + return reinterpret_cast(iter->data); + } + }; + + explicit CutterRzList(const RzList *l) : list(l) {} + iterator begin() const + { + if (!list) { + return iterator(nullptr); + } + return iterator(list->head); + } + iterator end() const { return iterator(nullptr); } +}; + +#endif // RIZINCPP_H diff --git a/src/dialogs/EditMethodDialog.cpp b/src/dialogs/EditMethodDialog.cpp index c9e9fdfc..9637b69b 100644 --- a/src/dialogs/EditMethodDialog.cpp +++ b/src/dialogs/EditMethodDialog.cpp @@ -86,11 +86,7 @@ bool EditMethodDialog::inputValid() QString EditMethodDialog::convertRealNameToName(const QString &realName) { - std::unique_ptr sanitizedCString( - rz_str_sanitize_sdb_key(realName.toUtf8().constData()), - [](const char *s) { rz_mem_free((void*)s); }); - - return QString(sanitizedCString.get()); + return fromOwnedCharPtr(rz_str_sanitize_sdb_key(realName.toUtf8().constData())); } void EditMethodDialog::setClass(const QString &className) diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index ddbfea65..50277c7d 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -314,9 +314,8 @@ void DisassemblyContextMenu::addDebugMenu() QVector DisassemblyContextMenu::getThingUsedHere(RVA offset) { RzCoreLocked core(Core()); - auto p = std::unique_ptr { - rz_core_analysis_name(core, offset), rz_core_analysis_name_free - }; + auto p = fromOwned( + rz_core_analysis_name(core, offset), rz_core_analysis_name_free); if (!p) { return {}; } diff --git a/src/widgets/CallGraph.cpp b/src/widgets/CallGraph.cpp index 2fbf603d..8c3d7e4a 100644 --- a/src/widgets/CallGraph.cpp +++ b/src/widgets/CallGraph.cpp @@ -74,7 +74,6 @@ static inline bool isBetween(ut64 a, ut64 x, ut64 b) return (a == UT64_MAX || a <= x) && (b == UT64_MAX || x <= b); } -using PRzList = std::unique_ptr; void CallGraphView::loadCurrentGraph() { @@ -90,7 +89,7 @@ void CallGraphView::loadCurrentGraph() GraphLayout::GraphBlock block; block.entry = fcn->addr; - auto xrefs = PRzList { rz_analysis_function_get_xrefs_from(fcn), rz_list_free }; + auto xrefs = fromOwned(rz_analysis_function_get_xrefs_from(fcn)); auto calls = std::unordered_set(); for (const auto &xref : CutterRzList(xrefs.get())) { const auto x = xref->to; diff --git a/src/widgets/DisassemblerGraphView.cpp b/src/widgets/DisassemblerGraphView.cpp index 63bf6832..fbd5ae39 100644 --- a/src/widgets/DisassemblerGraphView.cpp +++ b/src/widgets/DisassemblerGraphView.cpp @@ -193,9 +193,7 @@ void DisassemblerGraphView::loadCurrentGraph() windowTitle = tr("Graph"); if (fcn && RZ_STR_ISNOTEMPTY(fcn->name)) { - std::unique_ptr fcnName { - rz_str_escape_utf8_for_json(fcn->name, -1), std::free - }; + auto fcnName = fromOwned(rz_str_escape_utf8_for_json(fcn->name, -1)); windowTitle += QString("(%0)").arg(fcnName.get()); } else { windowTitle += "(Empty)"; @@ -267,10 +265,8 @@ void DisassemblerGraphView::loadCurrentGraph() } rz_io_read_at(core->io, bbi->addr, buf.get(), (int)bbi->size); - std::unique_ptr vec { - rz_pvector_new(reinterpret_cast(rz_analysis_disasm_text_free)), - rz_pvector_free - }; + auto vec = fromOwned( + rz_pvector_new(reinterpret_cast(rz_analysis_disasm_text_free))); if (!vec) { break; } diff --git a/src/widgets/FunctionsWidget.cpp b/src/widgets/FunctionsWidget.cpp index 864780c4..61dad3fe 100644 --- a/src/widgets/FunctionsWidget.cpp +++ b/src/widgets/FunctionsWidget.cpp @@ -234,12 +234,10 @@ QVariant FunctionModel::data(const QModelIndex &index, int role) const QStringList summary {}; { auto seeker = Core()->seekTemp(function.offset); - auto strings = std::unique_ptr { + auto strings = fromOwnedCharPtr( rz_core_print_disasm_strings(Core()->core(), RZ_CORE_DISASM_STRINGS_MODE_FUNCTION, - 0, NULL), - free - }; - summary = QString(strings.get()).split('\n', CUTTER_QT_SKIP_EMPTY_PARTS); + 0, NULL)); + summary = strings.split('\n', CUTTER_QT_SKIP_EMPTY_PARTS); } const QFont &fnt = Config()->getFont(); diff --git a/src/widgets/VisualNavbar.cpp b/src/widgets/VisualNavbar.cpp index 733430ca..3807fe0b 100644 --- a/src/widgets/VisualNavbar.cpp +++ b/src/widgets/VisualNavbar.cpp @@ -21,8 +21,7 @@ VisualNavbar::VisualNavbar(MainWindow *main, QWidget *parent) graphicsView(new QGraphicsView), seekGraphicsItem(nullptr), PCGraphicsItem(nullptr), - main(main), - stats(nullptr, rz_core_analysis_stats_free) + main(main) { Q_UNUSED(parent); @@ -119,7 +118,7 @@ void VisualNavbar::fetchStats() RzCoreLocked core(Core()); stats.reset(nullptr); - RzList *list = rz_core_get_boundaries_prot(core, -1, NULL, "search"); + auto list = fromOwned(rz_core_get_boundaries_prot(core, -1, NULL, "search")); if (!list) { return; } @@ -127,7 +126,7 @@ void VisualNavbar::fetchStats() RzIOMap *map; ut64 from = UT64_MAX; ut64 to = 0; - CutterRzListForeach (list, iter, RzIOMap, map) { + CutterRzListForeach (list.get(), iter, RzIOMap, map) { ut64 f = rz_itv_begin(map->itv); ut64 t = rz_itv_end(map->itv); if (f < from) { @@ -137,7 +136,6 @@ void VisualNavbar::fetchStats() to = t; } } - rz_list_free(list); to--; // rz_core_analysis_get_stats takes inclusive ranges if (to < from) { return; diff --git a/src/widgets/VisualNavbar.h b/src/widgets/VisualNavbar.h index 848f7b18..6fdc6ceb 100644 --- a/src/widgets/VisualNavbar.h +++ b/src/widgets/VisualNavbar.h @@ -47,7 +47,7 @@ private: QGraphicsRectItem *PCGraphicsItem; MainWindow *main; - std::unique_ptr stats; + UniquePtrC stats; unsigned int statsWidth = 0; unsigned int previousWidth = 0; From 756850ae27c1ca34b4c9d2d1a81c69d9c79409b9 Mon Sep 17 00:00:00 2001 From: billow Date: Sun, 14 Aug 2022 17:10:18 +0800 Subject: [PATCH 23/77] Convert cmds {`.`, `afv[WR]j`} (#3017) * remove `asyncCmd` `asyncCmdEsil`, `cmdjTask`, `cmdEsil`, `cmdjAt` * convert `afv[WR]j` --- rizin | 2 +- src/common/RunScriptTask.cpp | 5 +- src/core/Cutter.cpp | 120 ++++++++++------------------------- src/core/Cutter.h | 41 +----------- src/dialogs/AboutDialog.cpp | 2 - 5 files changed, 41 insertions(+), 129 deletions(-) diff --git a/rizin b/rizin index a9c59ce1..32ef1713 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit a9c59ce1ddc44c498b6a9a02e36989a34f184286 +Subproject commit 32ef171354e2fde6194a354a1a350df357c08afc diff --git a/src/common/RunScriptTask.cpp b/src/common/RunScriptTask.cpp index 7bac2d99..ca830b1a 100644 --- a/src/common/RunScriptTask.cpp +++ b/src/common/RunScriptTask.cpp @@ -16,7 +16,10 @@ void RunScriptTask::runTask() { if (!this->fileName.isNull()) { log(tr("Executing script...")); - Core()->cmdTask(". " + this->fileName); + Core()->functionTask([&](RzCore *core) { + rz_core_run_script(core, this->fileName.toUtf8().constData()); + return nullptr; + }); if (isInterrupted()) { return; } diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index ac8634b1..513806ff 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -430,48 +430,6 @@ bool CutterCore::isDebugTaskInProgress() return false; } -bool CutterCore::asyncCmdEsil(const char *command, QSharedPointer &task) -{ - asyncCmd(command, task); - - if (task.isNull()) { - return false; - } - - connect(task.data(), &RizinCmdTask::finished, task.data(), [this, task]() { - QString res = qobject_cast(task.data())->getResult(); - - if (res.contains(QStringLiteral("[ESIL] Stopped execution in an invalid instruction"))) { - msgBox.showMessage("Stopped when attempted to run an invalid instruction. You can " - "disable this in Preferences"); - } - }); - - return true; -} - -bool CutterCore::asyncCmd(const char *str, QSharedPointer &task) -{ - if (!task.isNull()) { - return false; - } - - CORE_LOCK(); - - RVA offset = core->offset; - - task = QSharedPointer(new RizinCmdTask(str, true)); - connect(task.data(), &RizinTask::finished, task.data(), [this, offset, task]() { - CORE_LOCK(); - - if (offset != core->offset) { - updateSeek(); - } - }); - - return true; -} - bool CutterCore::asyncTask(std::function fcn, QSharedPointer &task) { if (!task.isNull()) { @@ -492,6 +450,13 @@ bool CutterCore::asyncTask(std::function fcn, QSharedPointer fcn) +{ + auto task = std::unique_ptr(new RizinFunctionTask(std::move(fcn), true)); + task->startTask(); + task->joinTask(); +} + QString CutterCore::cmdRawAt(const char *cmd, RVA address) { QString res; @@ -534,18 +499,6 @@ CutterJson CutterCore::cmdj(const char *str) return parseJson(res, str); } -CutterJson CutterCore::cmdjAt(const char *str, RVA address) -{ - CutterJson res; - RVA oldOffset = getOffset(); - seekSilent(address); - - res = cmdj(str); - - seekSilent(oldOffset); - return res; -} - QString CutterCore::cmdTask(const QString &str) { RizinCmdTask task(str); @@ -554,14 +507,6 @@ QString CutterCore::cmdTask(const QString &str) return task.getResult(); } -CutterJson CutterCore::cmdjTask(const QString &str) -{ - RizinCmdTask task(str); - task.startTask(); - task.joinTask(); - return task.getResultJson(); -} - CutterJson CutterCore::parseJson(char *res, const char *cmd) { if (!res) { @@ -1393,16 +1338,6 @@ QString CutterCore::flagAt(RVA addr) return core->flags->realnames && f->realname ? f->realname : f->name; } -void CutterCore::cmdEsil(const char *command) -{ - // use cmd and not cmdRaw because of unexpected commands - QString res = cmd(command); - if (res.contains(QStringLiteral("[ESIL] Stopped execution in an invalid instruction"))) { - msgBox.showMessage("Stopped when attempted to run an invalid instruction. You can disable " - "this in Preferences"); - } -} - void CutterCore::createFunctionAt(RVA addr) { createFunctionAt(addr, ""); @@ -4027,22 +3962,35 @@ QList CutterCore::getAllSearch(QString searchFor, QString spa QList CutterCore::getXRefsForVariable(QString variableName, bool findWrites, RVA offset) { + CORE_LOCK(); + auto fcn = functionIn(offset); + if (!fcn) { + return {}; + } + const auto typ = + findWrites ? RZ_ANALYSIS_VAR_ACCESS_TYPE_WRITE : RZ_ANALYSIS_VAR_ACCESS_TYPE_READ; QList xrefList = QList(); - for (CutterJson xrefObject : cmdjAt(findWrites ? "afvWj" : "afvRj", offset)) { - QString name = xrefObject[RJsonKey::name].toString(); - if (name == variableName) { - for (CutterJson address : xrefObject[RJsonKey::addrs]) { - XrefDescription xref; - RVA addr = address.toRVA(); - xref.from = addr; - xref.to = addr; - if (findWrites) { - xref.from_str = RzAddressString(addr); - } else { - xref.to_str = RzAddressString(addr); - } - xrefList << xref; + RzList *vars = rz_analysis_var_all_list(core->analysis, fcn); + for (const auto &v : CutterRzList(vars)) { + if (variableName != v->name) { + continue; + } + RzAnalysisVarAccess *acc; + CutterRzVectorForeach(&v->accesses, acc, RzAnalysisVarAccess) + { + if (!(acc->type & typ)) { + continue; } + XrefDescription xref; + RVA addr = fcn->addr + acc->offset; + xref.from = addr; + xref.to = addr; + if (findWrites) { + xref.from_str = RzAddressString(addr); + } else { + xref.to_str = RzAddressString(addr); + } + xrefList << xref; } } return xrefList; diff --git a/src/core/Cutter.h b/src/core/Cutter.h index c9b92636..7d8aa098 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -98,21 +98,6 @@ public: */ QString cmd(const char *str); QString cmd(const QString &str) { return cmd(str.toUtf8().constData()); } - /** - * @brief send a command to Rizin asynchronously - * @param str the command you want to execute - * @param task a shared pointer that will be returned with the Rizin command task - * @note connect to the &RizinTask::finished signal to add your own logic once - * the command is finished. Use task->getResult()/getResultJson() for the - * return value. - * Once you have setup connections you can start the task with task->startTask() - * If you want to seek to an address, you should use CutterCore::seek. - */ - bool asyncCmd(const char *str, QSharedPointer &task); - bool asyncCmd(const QString &str, QSharedPointer &task) - { - return asyncCmd(str.toUtf8().constData(), task); - } /** * @brief send a task to Rizin @@ -120,6 +105,7 @@ public: * @return execute successful? */ bool asyncTask(std::function fcn, QSharedPointer &task); + void functionTask(std::function fcn); /** * @brief Execute a Rizin command \a cmd. By nature, the API @@ -188,31 +174,8 @@ public: CutterJson cmdj(const char *str); CutterJson cmdj(const QString &str) { return cmdj(str.toUtf8().constData()); } - CutterJson cmdjAt(const char *str, RVA address); QString cmdTask(const QString &str); - CutterJson cmdjTask(const QString &str); - /** - * @brief send a command to Rizin and check for ESIL errors - * @param command the command you want to execute - * @note If you want to seek to an address, you should use CutterCore::seek. - */ - void cmdEsil(const char *command); - void cmdEsil(const QString &command) { cmdEsil(command.toUtf8().constData()); } - /** - * @brief send a command to Rizin and check for ESIL errors - * @param command the command you want to execute - * @param task a shared pointer that will be returned with the Rizin command task - * @note connect to the &RizinTask::finished signal to add your own logic once - * the command is finished. Use task->getResult()/getResultJson() for the - * return value. - * Once you have setup connections you can start the task with task->startTask() - * If you want to seek to an address, you should use CutterCore::seek. - */ - bool asyncCmdEsil(const char *command, QSharedPointer &task); - bool asyncCmdEsil(const QString &command, QSharedPointer &task) - { - return asyncCmdEsil(command.toUtf8().constData(), task); - } + QString getRizinVersionReadable(const char *program = nullptr); QString getVersionInformation(); diff --git a/src/dialogs/AboutDialog.cpp b/src/dialogs/AboutDialog.cpp index d60a5566..aaca9b47 100644 --- a/src/dialogs/AboutDialog.cpp +++ b/src/dialogs/AboutDialog.cpp @@ -1,4 +1,3 @@ -#include "rz_version.h" #include "core/Cutter.h" #include "AboutDialog.h" @@ -7,7 +6,6 @@ #include "common/Configuration.h" #include "common/BugReporting.h" - #include #include #include From 550c416c0477a1eeb13ccc97a05ee445fe52149f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C4=81rlis=20Se=C5=86ko?= Date: Fri, 12 Aug 2022 16:53:43 +0300 Subject: [PATCH 24/77] Don't refresh graph breakpoints during redraw. --- src/widgets/DisassemblerGraphView.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/widgets/DisassemblerGraphView.cpp b/src/widgets/DisassemblerGraphView.cpp index fbd5ae39..008e8fd5 100644 --- a/src/widgets/DisassemblerGraphView.cpp +++ b/src/widgets/DisassemblerGraphView.cpp @@ -170,6 +170,7 @@ void DisassemblerGraphView::refreshView() { CutterGraphView::refreshView(); loadCurrentGraph(); + breakpoints = Core()->getBreakpointsAddresses(); emit viewRefreshed(); } @@ -370,8 +371,6 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block, p.setFont(Config()->getFont()); p.drawRect(blockRect); - breakpoints = Core()->getBreakpointsAddresses(); - // Render node DisassemblyBlock &db = disassembly_blocks[block.entry]; bool block_selected = false; From ee7cfc1b89abc4537457ee8f4a83c8e2968daa70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C4=81rlis=20Se=C5=86ko?= Date: Mon, 15 Aug 2022 16:01:46 +0300 Subject: [PATCH 25/77] Make library list in dashboard scrollbable when needed #3019 --- src/widgets/Dashboard.cpp | 30 +++++++++--------------------- src/widgets/Dashboard.ui | 27 +++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/widgets/Dashboard.cpp b/src/widgets/Dashboard.cpp index f73b4cc1..1df65b32 100644 --- a/src/widgets/Dashboard.cpp +++ b/src/widgets/Dashboard.cpp @@ -133,33 +133,21 @@ void Dashboard::updateContents() setPlainText(ui->codeSizeLineEdit, QString::number(code) + " bytes"); setPlainText(ui->percentageLineEdit, QString::number(precentage) + "%"); - // dunno: why not label->setText(lines.join("\n")? - while (ui->verticalLayout_2->count() > 0) { - QLayoutItem *item = ui->verticalLayout_2->takeAt(0); - if (item != nullptr) { - QWidget *w = item->widget(); - if (w != nullptr) { - w->deleteLater(); - } - - delete item; - } - } - + ui->libraryList->setPlainText(""); const RzList *libs = bf ? rz_bin_object_get_libs(bf->o) : nullptr; if (libs) { + QString libText; + bool first = true; for (const auto &lib : CutterRzList(libs)) { - auto *label = new QLabel(this); - label->setText(lib); - label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - label->setTextInteractionFlags(Qt::TextSelectableByMouse); - ui->verticalLayout_2->addWidget(label); + if (!first) { + libText.append("\n"); + } + libText.append(lib); + first = false; } + ui->libraryList->setPlainText(libText); } - QSpacerItem *spacer = new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding); - ui->verticalLayout_2->addSpacerItem(spacer); - // Check if signature info and version info available if (!Core()->getSignatureInfo().size()) { ui->certificateButton->setEnabled(false); diff --git a/src/widgets/Dashboard.ui b/src/widgets/Dashboard.ui index 521da7f2..fe1b7e5a 100644 --- a/src/widgets/Dashboard.ui +++ b/src/widgets/Dashboard.ui @@ -42,7 +42,7 @@ QFrame::Plain - Qt::ScrollBarAlwaysOff + Qt::ScrollBarAsNeeded Qt::ScrollBarAlwaysOff @@ -1331,10 +1331,33 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + - + + + Qt::ScrollBarAlwaysOff + + + QPlainTextEdit::NoWrap + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + From cab85d204ea008e365ba95037e57c0fc424b5ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C4=81rlis=20Se=C5=86ko?= Date: Mon, 15 Aug 2022 11:02:08 +0300 Subject: [PATCH 26/77] Set org domain to fix alt tab icon on Linux #3022 --- src/Main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Main.cpp b/src/Main.cpp index 8cb4347f..9c45f5e3 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -74,6 +74,9 @@ int main(int argc, char *argv[]) qRegisterMetaType>(); QCoreApplication::setOrganizationName("rizin"); +#ifndef Q_OS_MACOS // don't set on macOS so that it doesn't affect config path there + QCoreApplication::setOrganizationDomain("rizin.re"); +#endif QCoreApplication::setApplicationName("cutter"); // Importing settings after setting rename, needs separate handling in addition to regular version to version upgrade. From 7a83001a067c7b0190ca586b1089111da59df8b6 Mon Sep 17 00:00:00 2001 From: billow Date: Thu, 18 Aug 2022 06:52:50 +0800 Subject: [PATCH 27/77] convert `?E` to API (#3025) --- src/plugins/sample-cpp/CMakeLists.txt | 3 ++- src/plugins/sample-cpp/CutterSamplePlugin.cpp | 19 ++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/plugins/sample-cpp/CMakeLists.txt b/src/plugins/sample-cpp/CMakeLists.txt index 93e140f5..53e264c2 100644 --- a/src/plugins/sample-cpp/CMakeLists.txt +++ b/src/plugins/sample-cpp/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.12) project(cutter-sample-plugin) find_package(Cutter REQUIRED) +find_package(Rizin REQUIRED) set(CUTTER_INSTALL_PLUGDIR "${Cutter_USER_PLUGINDIR}" CACHE STRING "Directory to install Cutter plugin into") set(CMAKE_AUTOMOC ON) @@ -9,5 +10,5 @@ set(CMAKE_AUTOMOC ON) add_library(sample_plugin MODULE CutterSamplePlugin.h CutterSamplePlugin.cpp) -target_link_libraries(sample_plugin PRIVATE Cutter::Cutter) +target_link_libraries(sample_plugin PRIVATE Cutter::Cutter Rizin::Core) install(TARGETS sample_plugin DESTINATION "${CUTTER_INSTALL_PLUGDIR}") diff --git a/src/plugins/sample-cpp/CutterSamplePlugin.cpp b/src/plugins/sample-cpp/CutterSamplePlugin.cpp index bb2264b3..dae6443a 100644 --- a/src/plugins/sample-cpp/CutterSamplePlugin.cpp +++ b/src/plugins/sample-cpp/CutterSamplePlugin.cpp @@ -8,6 +8,7 @@ #include #include #include +#include void CutterSamplePlugin::setupPlugin() {} @@ -46,25 +47,21 @@ CutterSamplePluginWidget::CutterSamplePluginWidget(MainWindow *main) : CutterDoc void CutterSamplePluginWidget::on_seekChanged(RVA addr) { Q_UNUSED(addr); - QString res; - { - TempConfig tempConfig; - tempConfig.set("scr.color", 0); - res = Core()->cmd("?E `pi 1`"); - } + RzCoreLocked core(Core()); + TempConfig tempConfig; + tempConfig.set("scr.color", 0); + QString disasm = Core()->disassembleSingleInstruction(Core()->getOffset()); + QString res = fromOwnedCharPtr(rz_core_clippy(core, disasm.toUtf8().constData())); text->setText(res); } void CutterSamplePluginWidget::on_buttonClicked() { RzCoreLocked core(Core()); - char *fortune = rz_core_fortune_get_random(core); + auto fortune = fromOwned(rz_core_fortune_get_random(core)); if (!fortune) { return; } - // cmdRaw can be used to execute single raw commands - // this is especially good for user-controlled input - QString res = Core()->cmdRaw("?E " + QString::fromUtf8(fortune)); + QString res = fromOwnedCharPtr(rz_core_clippy(core, fortune.get())); text->setText(res); - rz_mem_free(fortune); } From e6db27135dcb9f2aa46bde91ac5c82e64ed51df3 Mon Sep 17 00:00:00 2001 From: Edd Barrett Date: Thu, 18 Aug 2022 15:18:40 +0000 Subject: [PATCH 28/77] Fix build on 32-bit systems. (#3032) --- src/widgets/GraphGridLayout.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/GraphGridLayout.cpp b/src/widgets/GraphGridLayout.cpp index cf100e24..0cf7840d 100644 --- a/src/widgets/GraphGridLayout.cpp +++ b/src/widgets/GraphGridLayout.cpp @@ -554,7 +554,7 @@ void GraphGridLayout::calculateEdgeMainColumn(GraphGridLayout::LayoutState &stat struct Event { - size_t blockId; + ut64 blockId; size_t edgeId; int row; enum Type { Edge = 0, Block = 1 } type; From d6370541e7e0f2ca3851b6e839925373c842e25c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Wed, 24 Aug 2022 15:51:00 +0200 Subject: [PATCH 29/77] Generate and deploy source tarball (Fix #2878) (#3036) This builds a real tarball, as opposed to the flawed GitHub-generated one, and also includes the following changes: Individual builds now have dedicated names like "linux-x86_64". The structure in the yml is now very similar to how it is in rizin. Since that means builds are renamed, the filename has also been changed from the meaningless "ccpp.yml" to "ci.yml", as that would have happened sooner or later anyway and now will not produce additional intermediate rename states. The workflow name inside that file is now also just "CI" since adding "Cutter" there is redundant. --- .github/workflows/{ccpp.yml => ci.yml} | 56 +++++++++++++++++++++----- scripts/tarball.sh | 32 +++++++++++++++ 2 files changed, 77 insertions(+), 11 deletions(-) rename .github/workflows/{ccpp.yml => ci.yml} (85%) create mode 100755 scripts/tarball.sh diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ci.yml similarity index 85% rename from .github/workflows/ccpp.yml rename to .github/workflows/ci.yml index ff9bde5f..edd72529 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Cutter CI +name: CI on: push: @@ -16,25 +16,48 @@ on: jobs: build: + name: ${{ matrix.name }} runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04, ubuntu-18.04, macos-latest, windows-2019] - python-version: [3.7.x] - system-deps: [false] - cc-override: [default] - cxx-override: [default] + name: [ + linux-x86_64, + linux-x86_64-system-deps, + macos-x86_64, + windows-x86_64, + tarball + ] include: - - os: windows-2019 + - name: windows-x86_64 + os: windows-2019 package: true - - os: ubuntu-18.04 # ensure that Cutter can be built at least in basic config on Ubuntu 18.04 using sytem libraries + system-deps: false + python-version: 3.7.x + - name: linux-x86_64-system-deps # ensure that Cutter can be built at least in basic config on Ubuntu 18.04 using sytem libraries + os: ubuntu-18.04 python-version: 3.6.x system-deps: true cc-override: '/usr/bin/gcc-7' cxx-override: '/usr/bin/g++-7' - - os: ubuntu-18.04 # release package build + - name: linux-x86_64 + os: ubuntu-18.04 + python-version: 3.7.x system-deps: false package: true + cc-override: default + cxx-override: default + - name: macos-x86_64 + os: macos-latest + python-version: 3.7.x + system-deps: false + package: true + cc-override: default + cxx-override: default + - name: tarball + python-version: 3.7.x + os: ubuntu-20.04 + system-deps: false + tarball: true # Prevent one job from pausing the rest fail-fast: false steps: @@ -159,6 +182,7 @@ jobs: export APPIMAGE_FILE="Cutter-${PACKAGE_ID}-Linux-x86_64.AppImage" mv Cutter-*-x86_64.AppImage "$APPIMAGE_FILE" echo PACKAGE_NAME=$APPIMAGE_FILE >> $GITHUB_ENV + echo PACKAGE_PATH=build/$APPIMAGE_FILE >> $GITHUB_ENV echo UPLOAD_ASSET_TYPE=application/x-executable >> $GITHUB_ENV fi - name: cmake macos @@ -199,6 +223,7 @@ jobs: make package export CUTTER_VERSION=$(python3 ../scripts/get_version.py) echo PACKAGE_NAME=${PACKAGE_NAME}.dmg >> $GITHUB_ENV + echo PACKAGE_PATH=build/${PACKAGE_NAME}.dmg >> $GITHUB_ENV echo UPLOAD_ASSET_TYPE=application/x-apple-diskimage >> $GITHUB_ENV - name: windows dependencies if: contains(matrix.os, 'windows') @@ -241,12 +266,21 @@ jobs: cmake --build . --config Release cmake --build . --config Release --target package echo PACKAGE_NAME=%PACKAGE_NAME%.zip >> %GITHUB_ENV% + echo PACKAGE_PATH=build/%PACKAGE_NAME%.zip >> %GITHUB_ENV% echo UPLOAD_ASSET_TYPE=application/zip >> %GITHUB_ENV% + - name: Create tarball + if: matrix.tarball + shell: bash + run: | + scripts/tarball.sh "Cutter-${PACKAGE_ID}" + echo PACKAGE_NAME=Cutter-${PACKAGE_ID}-src.tar.gz >> $GITHUB_ENV + echo PACKAGE_PATH=Cutter-${PACKAGE_ID}-src.tar.gz >> $GITHUB_ENV + echo UPLOAD_ASSET_TYPE=application/gzip >> $GITHUB_ENV - uses: actions/upload-artifact@v2 if: env.PACKAGE_NAME != null with: name: ${{ env.PACKAGE_NAME }} - path: build/${{ env.PACKAGE_NAME }} + path: ${{ env.PACKAGE_PATH }} - name: Get release if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') id: get_release @@ -260,6 +294,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.get_release.outputs.upload_url }} - asset_path: build/${{ env.PACKAGE_NAME }} + asset_path: ${{ env.PACKAGE_PATH }} asset_name: ${{ env.PACKAGE_NAME }} asset_content_type: ${{ env.UPLOAD_ASSET_TYPE }} diff --git a/scripts/tarball.sh b/scripts/tarball.sh new file mode 100755 index 00000000..3876b449 --- /dev/null +++ b/scripts/tarball.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +NAME=${1:-Cutter} + +set -xe +cd $(dirname "${BASH_SOURCE[0]}")/.. + +shopt -s extglob +shopt -s dotglob +mkdir "${NAME}" +cp -r !(${NAME}) "${NAME}" + +pushd "${NAME}" +git clean -dxff . +git submodule update --init --recursive + +pushd rizin +git clean -dxff . +# Possible option: pre-download all subproject, however this makes the tarball huge. +# As opposed to meson dist used for rizin tarballs, this will not just download the ones +# used in a default build, but all of them, including multiple capstone variants. +# meson subprojects download +popd + +pushd src/translations +git clean -dxff . +popd + +find . -name ".git*" | xargs rm -rfv +popd + +tar -czvf "${NAME}-src.tar.gz" "${NAME}" From a2ed7971aad8946b4cdf93a540c27255e8f5b654 Mon Sep 17 00:00:00 2001 From: turlututututu Date: Fri, 2 Sep 2022 23:51:30 +0200 Subject: [PATCH 30/77] Update iterator synthax to be compatible with cpp17 (#3033) std::iterator is deprecated in cpp17 thus in order to be able to upgrade to cpp17 if needed we manually define the relevant types for iterator. See https://www.fluentcpp.com/2018/05/08/std-iterator-deprecated/ for more details. --- src/core/RizinCpp.h | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/core/RizinCpp.h b/src/core/RizinCpp.h index ae4fcb03..e39f964b 100644 --- a/src/core/RizinCpp.h +++ b/src/core/RizinCpp.h @@ -71,8 +71,15 @@ private: const RzPVector *const vec; public: - class iterator : public std::iterator + class iterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = T *; + using difference_type = ptrdiff_t; + using pointer = T **; + using reference = T *&; + private: T **p; @@ -107,8 +114,15 @@ private: const RzList *const list; public: - class iterator : public std::iterator + class iterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = T *; + using difference_type = ptrdiff_t; + using pointer = T **; + using reference = T *&; + private: RzListIter *iter; From 2e414069c5a5b6eae78c5ca5e3742ce55001f37a Mon Sep 17 00:00:00 2001 From: karliss Date: Tue, 6 Sep 2022 18:46:39 +0300 Subject: [PATCH 31/77] Support editing in hex editor (#3026) --- src/widgets/HexWidget.cpp | 1004 ++++++++++++++++++++++++++++++++++--- src/widgets/HexWidget.h | 178 +++++-- 2 files changed, 1084 insertions(+), 98 deletions(-) diff --git a/src/widgets/HexWidget.cpp b/src/widgets/HexWidget.cpp index 39f63186..d933b944 100644 --- a/src/widgets/HexWidget.cpp +++ b/src/widgets/HexWidget.cpp @@ -26,6 +26,7 @@ static constexpr uint64_t MAX_COPY_SIZE = 128 * 1024 * 1024; static constexpr int MAX_LINE_WIDTH_PRESET = 32; static constexpr int MAX_LINE_WIDTH_BYTES = 128 * 1024; +static constexpr int WARNING_TIME_MS = 500; HexWidget::HexWidget(QWidget *parent) : QScrollArea(parent), @@ -42,7 +43,8 @@ HexWidget::HexWidget(QWidget *parent) showHeader(true), showAscii(true), showExHex(true), - showExAddr(true) + showExAddr(true), + warningTimer(this) { setMouseTracking(true); setFocusPolicy(Qt::FocusPolicy::StrongFocus); @@ -103,7 +105,7 @@ HexWidget::HexWidget(QWidget *parent) actionItemBigEndian = new QAction(tr("Big Endian"), this); actionItemBigEndian->setCheckable(true); actionItemBigEndian->setEnabled(false); - connect(actionItemBigEndian, &QAction::triggered, this, &HexWidget::setItemEndianess); + connect(actionItemBigEndian, &QAction::triggered, this, &HexWidget::setItemEndianness); actionHexPairs = new QAction(tr("Bytes as pairs"), this); actionHexPairs->setCheckable(true); @@ -125,14 +127,14 @@ HexWidget::HexWidget(QWidget *parent) actionComment = new QAction(tr("Add Comment"), this); actionComment->setShortcutContext(Qt::ShortcutContext::WidgetWithChildrenShortcut); actionComment->setShortcut(Qt::Key_Semicolon); - connect(actionComment, &QAction::triggered, this, &HexWidget::on_actionAddComment_triggered); + connect(actionComment, &QAction::triggered, this, &HexWidget::onActionAddCommentTriggered); addAction(actionComment); // delete comment option actionDeleteComment = new QAction(tr("Delete Comment"), this); actionDeleteComment->setShortcutContext(Qt::ShortcutContext::WidgetWithChildrenShortcut); connect(actionDeleteComment, &QAction::triggered, this, - &HexWidget::on_actionDeleteComment_triggered); + &HexWidget::onActionDeleteCommentTriggered); addAction(actionDeleteComment); actionSelectRange = new QAction(tr("Select range"), this); @@ -183,8 +185,13 @@ HexWidget::HexWidget(QWidget *parent) connect(actionIncDec, &QAction::triggered, this, &HexWidget::w_increaseDecrease); actionsWriteOther.append(actionIncDec); + actionKeyboardEdit = new QAction(tr("Edit with keyboard"), this); + actionKeyboardEdit->setCheckable(true); + connect(actionKeyboardEdit, &QAction::triggered, this, &HexWidget::onKeyboardEditTriggered); + connect(actionKeyboardEdit, &QAction::toggled, this, &HexWidget::onKeyboardEditChanged); + connect(this, &HexWidget::selectionChanged, this, - [this](Selection selection) { actionCopy->setEnabled(!selection.empty); }); + [this](Selection newSelection) { actionCopy->setEnabled(!newSelection.empty); }); updateMetrics(); updateItemLength(); @@ -202,9 +209,10 @@ HexWidget::HexWidget(QWidget *parent) cursor.startBlinking(); updateColors(); -} -HexWidget::~HexWidget() {} + warningTimer.setSingleShot(true); + connect(&warningTimer, &QTimer::timeout, this, &HexWidget::hideWarningRect); +} void HexWidget::setMonospaceFont(const QFont &font) { @@ -228,6 +236,8 @@ void HexWidget::setItemSize(int nbytes) if (!values.contains(nbytes)) return; + finishEditingWord(); + itemByteLen = nbytes; if (itemByteLen > rowSizeBytes) { rowSizeBytes = itemByteLen; @@ -236,7 +246,12 @@ void HexWidget::setItemSize(int nbytes) actionsItemFormat.at(ItemFormatFloat)->setEnabled(nbytes >= 4); actionItemBigEndian->setEnabled(nbytes != 1); + refreshWordEditState(); + updateItemLength(); + if (!cursorOnAscii && cursor.address % itemByteLen) { + moveCursor(-int(cursor.address % itemByteLen)); + } fetchData(); updateCursorMeta(); @@ -245,6 +260,8 @@ void HexWidget::setItemSize(int nbytes) void HexWidget::setItemFormat(ItemFormat format) { + finishEditingWord(); + itemFormat = format; bool sizeEnabled = true; @@ -253,6 +270,8 @@ void HexWidget::setItemFormat(ItemFormat format) actionsItemSize.at(0)->setEnabled(sizeEnabled); actionsItemSize.at(1)->setEnabled(sizeEnabled); + refreshWordEditState(); + updateItemLength(); fetchData(); updateCursorMeta(); @@ -382,7 +401,7 @@ void HexWidget::selectRange(RVA start, RVA end) void HexWidget::clearSelection() { - setCursorAddr(cursor.address, false); + setCursorAddr(BasicCursor(cursor.address), false); emit selectionChanged(getSelection()); } @@ -393,7 +412,16 @@ HexWidget::Selection HexWidget::getSelection() void HexWidget::seek(uint64_t address) { - setCursorAddr(address); + if (!cursorOnAscii) { + // when other widget causes seek to the middle of word + // switch to ascii column which operates with byte positions + auto viewOffset = startAddress % itemByteLen; + auto addrOffset = address % itemByteLen; + if ((addrOffset + itemByteLen - viewOffset) % itemByteLen) { + setCursorOnAscii(true); + } + } + setCursorAddr(BasicCursor(address)); } void HexWidget::refresh() @@ -402,8 +430,9 @@ void HexWidget::refresh() viewport()->update(); } -void HexWidget::setItemEndianess(bool bigEndian) +void HexWidget::setItemEndianness(bool bigEndian) { + finishEditingWord(); itemBigEndian = bigEndian; updateCursorMeta(); // Update cached item character @@ -422,6 +451,7 @@ void HexWidget::updateColors() defColor = Config()->getColor("btext"); addrColor = Config()->getColor("func_var_addr"); diffColor = Config()->getColor("graph.diff.unmatch"); + warningColor = QColor("red"); updateCursorMeta(); viewport()->update(); @@ -450,6 +480,11 @@ void HexWidget::paintEvent(QPaintEvent *event) drawItemArea(painter); drawAsciiArea(painter); + if (warningRectVisible) { + painter.setPen(warningColor); + painter.drawRect(warningRect); + } + if (!cursorEnabled) return; @@ -467,6 +502,11 @@ void HexWidget::updateWidth() horizontalScrollBar()->setSingleStep(charWidth); } +bool HexWidget::isFixedWidth() const +{ + return itemFormat == ItemFormatHex || itemFormat == ItemFormatOct; +} + void HexWidget::resizeEvent(QResizeEvent *event) { int oldByteCount = bytesPerScreen(); @@ -526,22 +566,98 @@ void HexWidget::mousePressEvent(QMouseEvent *event) if (event->button() == Qt::LeftButton) { bool selectingData = itemArea.contains(pos); bool selecting = selectingData || asciiArea.contains(pos); + bool holdingShift = event->modifiers() == Qt::ShiftModifier; + + // move cursor within actively edited item + if (selectingData && !holdingShift && editWordState >= EditWordState::WriteNotEdited) { + + auto editWordArea = itemRectangle(cursor.address - startAddress); + if (editWordArea.contains(pos)) { + int wordOffset = 0; + auto cursorPosition = screenPosToAddr(pos, false, &wordOffset); + if (cursorPosition.address == cursor.address + // allow selecting after last character only when cursor limited to current word + && (wordOffset < editWord.length() + || navigationMode == HexNavigationMode::WordChar)) { + editWordPos = std::max(0, wordOffset); + editWordPos = std::min(editWordPos, editWord.length()); + + if (isFixedWidth()) { + updatingSelection = true; + auto selectionCursor = cursorPosition; + if (editWordPos > itemCharLen / 2) { + selectionCursor += itemByteLen; + } + selection.init(selectionCursor); + } + + viewport()->update(); + return; + } + } + } + + // cursor within any item if the mode allows + if (selectingData && !holdingShift && editWordState >= EditWordState::WriteNotStarted + && navigationMode == HexNavigationMode::AnyChar) { + updatingSelection = true; + setCursorOnAscii(false); + int wordOffset = 0; + auto cursorPosition = screenPosToAddr(pos, false, &wordOffset); + finishEditingWord(); + if (isFixedWidth() && wordOffset >= itemCharLen - itemPrefixLen) { + wordOffset = 0; + cursorPosition += itemByteLen; + } + setCursorAddr(cursorPosition, holdingShift); + auto selectionPosition = currentAreaPosToAddr(pos, true); + selection.init(selectionPosition); + emit selectionChanged(getSelection()); + + if (wordOffset > 0) { + startEditWord(); + editWordPos = std::min(wordOffset, editWord.length() - 1); + } + viewport()->update(); + return; + } + if (selecting) { + finishEditingWord(); + updatingSelection = true; setCursorOnAscii(!selectingData); auto cursorPosition = currentAreaPosToAddr(pos, true); - setCursorAddr(cursorPosition, event->modifiers() == Qt::ShiftModifier); + setCursorAddr(cursorPosition, holdingShift); viewport()->update(); } } } +void HexWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + QPoint pos(event->pos()); + pos.rx() += horizontalScrollBar()->value(); + + if (event->button() == Qt::LeftButton && !isFixedWidth() + && editWordState == EditWordState::WriteNotStarted && itemArea.contains(pos)) { + int wordOffset = 0; + auto cursorPosition = screenPosToAddr(pos, false, &wordOffset); + setCursorAddr(cursorPosition, false); + startEditWord(); + int padding = std::max(0, itemCharLen - editWord.length()); + editWordPos = std::max(0, wordOffset - padding); + editWordPos = std::min(editWordPos, editWord.length()); + } +} + void HexWidget::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { if (selection.isEmpty()) { - selection.init(cursor.address); + selection.init(BasicCursor(cursor.address)); cursorEnabled = true; + viewport()->update(); } updatingSelection = false; } @@ -560,13 +676,14 @@ void HexWidget::wheelEvent(QWheelEvent *event) startAddress = 0; } else if (delta > 0 && data->maxIndex() < static_cast(bytesPerScreen())) { startAddress = 0; + } else if ((data->maxIndex() - startAddress) + <= static_cast(bytesPerScreen() + delta - 1)) { + startAddress = (data->maxIndex() - bytesPerScreen()) + 1; } else { startAddress += delta; } + fetchData(); - if ((data->maxIndex() - startAddress) <= static_cast(bytesPerScreen() + delta - 1)) { - startAddress = (data->maxIndex() - bytesPerScreen()) + 1; - } if (cursor.address >= startAddress && cursor.address <= lastVisibleAddr()) { /* Don't enable cursor blinking if selection isn't empty */ cursorEnabled = selection.isEmpty(); @@ -577,6 +694,304 @@ void HexWidget::wheelEvent(QWheelEvent *event) viewport()->update(); } +bool HexWidget::validCharForEdit(QChar digit) +{ + switch (itemFormat) { + case ItemFormatHex: + return (digit >= '0' && digit <= '9') || (digit >= 'a' && digit <= 'f') + || (digit >= 'A' && digit <= 'F'); + case ItemFormatOct: { + if (editWordPos > 0) { + return (digit >= '0' && digit <= '7'); + } else { + int bitsInMSD = (itemByteLen * 8) % 3; + int biggestDigit = (1 << bitsInMSD) - 1; + return digit >= '0' && digit <= char('0' + biggestDigit); + } + } + case ItemFormatDec: + return (digit >= '0' && digit <= '9'); + case ItemFormatSignedDec: + return (digit >= '0' && digit <= '9') || digit == '-'; + case ItemFormatFloat: + return (digit >= '0' && digit <= '9') || digit == '-' || digit == '+' || digit == '.' + || digit == ',' || digit == '+' || digit == 'e' || digit == 'E' || digit == 'i' + || digit == 'n' || digit == 'f' || digit == 'I' || digit == 'N' || digit == 'F' + || digit == 'a' || digit == 'A'; + } + + return false; +} + +void HexWidget::movePrevEditCharAny() +{ + if (!selection.isEmpty()) { + clearSelection(); + } + editWordPos -= 1; + if (editWordPos < 0) { + finishEditingWord(); + if (moveCursor(-itemByteLen, false, OverflowMove::Ignore)) { + startEditWord(); + editWordPos = editWord.length() - 1; + } + } + viewport()->update(); +} + +void HexWidget::typeOverwriteModeChar(QChar c) +{ + if (editWordState < EditWordState::WriteNotEdited || !isFixedWidth()) { + return; + } + editWord[editWordPos] = c; + editWordPos++; + editWordState = EditWordState::WriteEdited; + if (editWordPos >= editWord.length()) { + finishEditingWord(); + bool moved = moveCursor(itemByteLen, false, OverflowMove::Ignore); + startEditWord(); + if (!moved) { + editWordPos = editWord.length() - 1; + } + } +} + +HexWidget::HexNavigationMode HexWidget::defaultNavigationMode() +{ + switch (editWordState) { + case EditWordState::Read: + return HexNavigationMode::Words; + case EditWordState::WriteNotStarted: + return isFixedWidth() ? HexNavigationMode::AnyChar : HexNavigationMode::Words; + case EditWordState::WriteNotEdited: + case EditWordState::WriteEdited: + return isFixedWidth() ? HexNavigationMode::AnyChar : HexNavigationMode::WordChar; + } + return HexNavigationMode::Words; +} + +void HexWidget::refreshWordEditState() +{ + navigationMode = defaultNavigationMode(); +} + +bool HexWidget::handleAsciiWrite(QKeyEvent *event) +{ + if (!cursorOnAscii || !canKeyboardEdit()) { + return false; + } + if (event->key() == Qt::Key_Backspace || event->matches(QKeySequence::Backspace)) { + if (!selection.isEmpty()) { + writeZeros(selection.start(), selection.size()); + } else { + moveCursor(-1, false); + writeZeros(cursor.address, 1); + } + return true; + } + if (event->key() == Qt::Key_Delete || event->matches(QKeySequence::Delete)) { + if (!selection.isEmpty()) { + writeZeros(selection.start(), selection.size()); + } else { + writeZeros(cursor.address, 1); + moveCursor(1, false); + } + return true; + } + QString text; + if (event->matches(QKeySequence::Paste)) { + text = QApplication::clipboard()->text(); + if (text.length() <= 0) { + return false; + } + } else { + text = event->text(); + if (text.length() <= 0) { + return false; + } + QChar c = text[0]; + if (c <= '\x1f' || c == '\x7f') { + return false; + } + } + + auto bytes = text.toUtf8(); // TODO:#3028 use selected text encoding + auto address = getLocationAddress(); + clearSelection(); + data->write(reinterpret_cast(bytes.data()), address, bytes.length()); + seek(address + bytes.length()); + viewport()->update(); + return true; +} + +bool HexWidget::handleNumberWrite(QKeyEvent *event) +{ + if (editWordState < EditWordState::WriteNotStarted) { + return false; + } + bool overwrite = isFixedWidth(); + auto keyText = event->text(); + bool editingWord = editWordState >= EditWordState::WriteNotEdited; + if (keyText.length() > 0 && validCharForEdit(keyText[0])) { + if (!selection.isEmpty()) { + setCursorAddr(BasicCursor(selection.start())); + } + if (!editingWord) { + startEditWord(); + } + if (overwrite) { + typeOverwriteModeChar(keyText[0]); + } else if (!editingWord /* && !overwrite */) { + editWord = keyText; + editWordPos = editWord.length(); + editWordState = EditWordState::WriteEdited; + } else if (itemFormat == ItemFormatFloat || editWord.length() < itemCharLen) { + editWord.insert(editWordPos, keyText); + editWordPos += keyText.length(); + editWordState = EditWordState::WriteEdited; + } + maybeFlushCharEdit(); + return true; + } + if (event->matches(QKeySequence::Paste) && (editingWord || overwrite)) { + QString text = QApplication::clipboard()->text(); + if (text.length() > 0) { + if (overwrite) { + startEditWord(); + for (QChar c : text) { + if (validCharForEdit(c)) { + typeOverwriteModeChar(c); + } + } + } else { + editWord.insert(editWordPos, text); + editWordPos += text.length(); + editWordState = EditWordState::WriteEdited; + } + } + maybeFlushCharEdit(); + return true; + } + if (editingWord) { + if (event->matches(QKeySequence::Cancel)) { + cancelEditedWord(); + return true; + } else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { + bool needToAdvance = + !(editWordPos == 0 && overwrite && editWordState < EditWordState::WriteEdited); + if (finishEditingWord(false) && needToAdvance) { + moveCursor(itemByteLen); + } + return true; + } + } + if (event->matches(QKeySequence::Delete)) { + if (!selection.isEmpty()) { + writeZeros(selection.start(), selection.size()); + clearSelection(); + } else { + startEditWord(); + if (overwrite) { + typeOverwriteModeChar('0'); + } else if (editWordPos < editWord.length()) { + editWord.remove(editWordPos, 1); + editWordState = EditWordState::WriteEdited; + } + } + maybeFlushCharEdit(); + return true; + } + if (event->matches(QKeySequence::DeleteEndOfWord) && selection.isEmpty()) { + startEditWord(); + if (overwrite) { + for (int i = editWordPos; i < editWord.length(); i++) { + typeOverwriteModeChar('0'); + } + } else if (editWordPos < editWord.length()) { + editWord.remove(editWordPos, editWord.length()); + editWordState = EditWordState::WriteEdited; + } + maybeFlushCharEdit(); + return true; + } + if (event->matches(QKeySequence::DeleteStartOfWord) && selection.isEmpty()) { + if (!editingWord || (overwrite && editWordPos == 0)) { + if (!moveCursor(-itemByteLen, false, OverflowMove::Ignore)) { + return false; + } + startEditWord(); + editWordPos = editWord.length(); + } + if (overwrite) { + while (editWordPos > 0) { + editWordPos--; + editWord[editWordPos] = '0'; + } + editWordState = EditWordState::WriteEdited; + maybeFlushCharEdit(); + return true; + } else { + if (editWordPos > 0) { + editWord.remove(0, editWordPos); + editWordState = EditWordState::WriteEdited; + editWordPos = 0; + maybeFlushCharEdit(); + return true; + } + } + return false; + } + + if (event->key() == Qt::Key_Backspace) { + if (!selection.isEmpty()) { + writeZeros(selection.start(), selection.size()); + clearSelection(); + setCursorAddr(BasicCursor(selection.start()), false); + return true; + } else { + if (!editingWord || (overwrite && editWordPos == 0)) { + if (!moveCursor(-itemByteLen, false, OverflowMove::Ignore)) { + return false; + } + startEditWord(); + editWordPos = editWord.length(); + } + if (editWordPos > 0) { + editWordPos -= 1; + if (overwrite) { + editWord[editWordPos] = '0'; + } else { + editWord.remove(editWordPos, 1); + } + editWordState = EditWordState::WriteEdited; + maybeFlushCharEdit(); + return true; + } + } + return false; + } + + return false; +} + +bool HexWidget::event(QEvent *event) +{ + // prefer treating keys like 's' 'g' '.' as typing input instead of global shortcuts + if (event->type() == QEvent::ShortcutOverride) { + auto keyEvent = static_cast(event); + auto modifiers = keyEvent->modifiers(); + if ((modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier + || modifiers == Qt::KeypadModifier) + && keyEvent->key() < Qt::Key_Escape && canKeyboardEdit()) { + keyEvent->accept(); + return true; + } + } + + return QScrollArea::event(event); +} + void HexWidget::keyPressEvent(QKeyEvent *event) { bool select = false; @@ -591,26 +1006,126 @@ void HexWidget::keyPressEvent(QKeyEvent *event) } return false; }; - if (moveOrSelect(QKeySequence::MoveToNextLine, QKeySequence::SelectNextLine)) { - moveCursor(itemRowByteLen(), select); - } else if (moveOrSelect(QKeySequence::MoveToPreviousLine, QKeySequence::SelectPreviousLine)) { - moveCursor(-itemRowByteLen(), select); - } else if (moveOrSelect(QKeySequence::MoveToNextChar, QKeySequence::SelectNextChar)) { - moveCursor(cursorOnAscii ? 1 : itemByteLen, select); - } else if (moveOrSelect(QKeySequence::MoveToPreviousChar, QKeySequence::SelectPreviousChar)) { - moveCursor(cursorOnAscii ? -1 : -itemByteLen, select); - } else if (moveOrSelect(QKeySequence::MoveToNextPage, QKeySequence::SelectNextPage)) { - moveCursor(bytesPerScreen(), select); - } else if (moveOrSelect(QKeySequence::MoveToPreviousPage, QKeySequence::SelectPreviousPage)) { - moveCursor(-bytesPerScreen(), select); - } else if (moveOrSelect(QKeySequence::MoveToStartOfLine, QKeySequence::SelectStartOfLine)) { - int linePos = int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); - moveCursor(-linePos, select); - } else if (moveOrSelect(QKeySequence::MoveToEndOfLine, QKeySequence::SelectEndOfLine)) { - int linePos = int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); - moveCursor(itemRowByteLen() - linePos, select); + + if (canKeyboardEdit()) { + if (handleAsciiWrite(event)) { + viewport()->update(); + return; + } + if (editWordState >= EditWordState::WriteNotStarted && !cursorOnAscii) { + if (handleNumberWrite(event)) { + viewport()->update(); + return; + } + } + } + + if (cursorOnAscii || navigationMode == HexNavigationMode::Words + || navigationMode == HexNavigationMode::AnyChar) { + if (moveOrSelect(QKeySequence::MoveToNextPage, QKeySequence::SelectNextPage)) { + moveCursor(bytesPerScreen(), select); + } else if (moveOrSelect(QKeySequence::MoveToPreviousPage, + QKeySequence::SelectPreviousPage)) { + moveCursor(-bytesPerScreen(), select); + } else if (moveOrSelect(QKeySequence::MoveToStartOfLine, QKeySequence::SelectStartOfLine)) { + int linePos = + int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); + moveCursor(-linePos, select); + } else if (moveOrSelect(QKeySequence::MoveToEndOfLine, QKeySequence::SelectEndOfLine)) { + int linePos = + int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); + moveCursor(itemRowByteLen() - linePos, select); + } + } + + if (navigationMode == HexNavigationMode::Words || cursorOnAscii) { + if (moveOrSelect(QKeySequence::MoveToNextLine, QKeySequence::SelectNextLine)) { + moveCursor(itemRowByteLen(), select, OverflowMove::Ignore); + } else if (moveOrSelect(QKeySequence::MoveToPreviousLine, + QKeySequence::SelectPreviousLine)) { + moveCursor(-itemRowByteLen(), select, OverflowMove::Ignore); + } else if (moveOrSelect(QKeySequence::MoveToNextChar, QKeySequence::SelectNextChar) + || moveOrSelect(QKeySequence::MoveToNextWord, QKeySequence::SelectNextWord)) { + moveCursor(cursorOnAscii ? 1 : itemByteLen, select); + } else if (moveOrSelect(QKeySequence::MoveToPreviousChar, QKeySequence::SelectPreviousChar) + || moveOrSelect(QKeySequence::MoveToPreviousWord, + QKeySequence::SelectPreviousWord)) { + moveCursor(cursorOnAscii ? -1 : -itemByteLen, select); + } + } else if (navigationMode == HexNavigationMode::AnyChar && !cursorOnAscii) { + if (moveOrSelect(QKeySequence::MoveToNextLine, QKeySequence::SelectNextLine)) { + moveCursorKeepEditOffset(itemRowByteLen(), select, OverflowMove::Ignore); + } else if (moveOrSelect(QKeySequence::MoveToPreviousLine, + QKeySequence::SelectPreviousLine)) { + moveCursorKeepEditOffset(-itemRowByteLen(), select, OverflowMove::Ignore); + } else if (moveOrSelect(QKeySequence::MoveToNextChar, QKeySequence::SelectNextChar)) { + if (select) { + moveCursor(itemByteLen, select); + } else { + if (!selection.isEmpty()) { + clearSelection(); + } + if (editWordState == EditWordState::WriteNotStarted) { + startEditWord(); + } + editWordPos += 1; + if (editWordPos >= editWord.length()) { + bool moved = moveCursor(itemByteLen, false, OverflowMove::Ignore); + startEditWord(); + if (!moved) { + editWordPos = editWord.length() - 1; + } + } + } + viewport()->update(); + } else if (event->matches(QKeySequence::MoveToPreviousChar)) { + movePrevEditCharAny(); + } else if (event->matches(QKeySequence::SelectPreviousChar)) { + moveCursor(-itemByteLen, true); + } else if (moveOrSelect(QKeySequence::MoveToNextWord, QKeySequence::SelectNextWord)) { + moveCursor(itemByteLen, select); + } else if (event->matches(QKeySequence::MoveToPreviousWord)) { + if (editWordPos > 0) { + editWordPos = 0; + viewport()->update(); + } else { + moveCursor(-itemByteLen, false); + } + } else if (event->matches(QKeySequence::SelectPreviousWord)) { + moveCursor(-itemByteLen, true); + } + } else if (navigationMode == HexNavigationMode::WordChar) { + if (event->matches(QKeySequence::MoveToNextChar)) { + editWordPos = std::min(editWord.length(), editWordPos + 1); + viewport()->update(); + } else if (event->matches(QKeySequence::MoveToPreviousChar)) { + editWordPos = std::max(0, editWordPos - 1); + viewport()->update(); + } else if (event->matches(QKeySequence::MoveToStartOfLine)) { + editWordPos = 0; + viewport()->update(); + } else if (event->matches(QKeySequence::MoveToEndOfLine)) { + editWordPos = editWord.length(); + viewport()->update(); + } else if (event->matches(QKeySequence::MoveToPreviousWord)) { + if (editWordPos > 0) { + editWordPos = 0; + } else { + moveCursor(-itemByteLen, select); + startEditWord(); + } + viewport()->update(); + } else if (event->matches(QKeySequence::MoveToNextWord)) { + if (editWordPos < editWord.length()) { + editWordPos = editWord.length(); + } else { + moveCursor(itemByteLen, select); + startEditWord(); + editWordPos = editWord.length(); + } + viewport()->update(); + } } - // viewport()->update(); } void HexWidget::contextMenuEvent(QContextMenuEvent *event) @@ -645,7 +1160,11 @@ void HexWidget::contextMenuEvent(QContextMenuEvent *event) actionComment->setText(tr("Edit Comment")); } - QMenu *menu = new QMenu(); + if (!ioModesController.canWrite()) { + actionKeyboardEdit->setChecked(false); + } + + auto *menu = new QMenu(this); QMenu *sizeMenu = menu->addMenu(tr("Item size:")); sizeMenu->addActions(actionsItemSize); QMenu *formatMenu = menu->addMenu(tr("Item format:")); @@ -657,6 +1176,8 @@ void HexWidget::contextMenuEvent(QContextMenuEvent *event) writeMenu->addActions(actionsWriteString); writeMenu->addSeparator(); writeMenu->addActions(actionsWriteOther); + menu->addAction(actionKeyboardEdit); + menu->addSeparator(); menu->addAction(actionCopy); disableOutsideSelectionActions(mouseOutsideSelection); @@ -700,23 +1221,20 @@ void HexWidget::copy() void HexWidget::copyAddress() { - uint64_t addr = cursor.address; - if (!selection.isEmpty()) { - addr = selection.start(); - } + uint64_t addr = getLocationAddress(); QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(RzAddressString(addr)); } // slot for add comment action -void HexWidget::on_actionAddComment_triggered() +void HexWidget::onActionAddCommentTriggered() { uint64_t addr = cursor.address; CommentsDialog::addOrEditComment(addr, this); } // slot for deleting comment action -void HexWidget::on_actionDeleteComment_triggered() +void HexWidget::onActionDeleteCommentTriggered() { uint64_t addr = cursor.address; Core()->delComment(addr); @@ -731,6 +1249,20 @@ void HexWidget::onRangeDialogAccepted() selectRange(rangeDialog.getStartAddress(), rangeDialog.getEndAddress()); } +void HexWidget::writeZeros(uint64_t address, uint64_t length) +{ + const uint64_t MAX_BUFFER = 1024; + std::vector zeroes(std::min(MAX_BUFFER, length), 0); + while (length > zeroes.size()) { + data->write(zeroes.data(), address, zeroes.size()); + address += zeroes.size(); + length -= zeroes.size(); + } + if (length > 0) { + data->write(zeroes.data(), address, length); + } +} + void HexWidget::w_writeString() { if (!ioModesController.prepareForWriting()) { @@ -825,10 +1357,7 @@ void HexWidget::w_writeZeros() return; } { - RzCoreLocked core(Core()); - auto *buf = (uint8_t *)calloc(len, sizeof(uint8_t)); - rz_core_write_at(core, getLocationAddress(), buf, len); - free(buf); + writeZeros(getLocationAddress(), len); } refresh(); } @@ -964,15 +1493,42 @@ void HexWidget::w_writeCString() refresh(); } +void HexWidget::onKeyboardEditTriggered(bool enabled) +{ + if (!enabled) { + return; + } + if (!ioModesController.prepareForWriting()) { + actionKeyboardEdit->setChecked(false); + } +} + +void HexWidget::onKeyboardEditChanged(bool enabled) +{ + if (!enabled) { + finishEditingWord(); + navigationMode = HexNavigationMode::Words; + editWordState = EditWordState::Read; + } else { + editWordState = EditWordState::WriteNotStarted; + navigationMode = defaultNavigationMode(); + } + updateCursorMeta(); + viewport()->update(); +} + void HexWidget::updateItemLength() { itemPrefixLen = 0; + itemPrefix.clear(); switch (itemFormat) { case ItemFormatHex: itemCharLen = 2 * itemByteLen; - if (itemByteLen > 1 && showExHex) + if (itemByteLen > 1 && showExHex) { itemPrefixLen = hexPrefix.length(); + itemPrefix = hexPrefix; + } break; case ItemFormatOct: itemCharLen = (itemByteLen * 8 + 3) / 3; @@ -1056,7 +1612,13 @@ void HexWidget::drawCursor(QPainter &painter, bool shadow) QPen pen(Qt::gray); pen.setStyle(Qt::DashLine); painter.setPen(pen); - shadowCursor.screenPos.setWidth(cursorOnAscii ? itemWidth() : charWidth); + qreal shadowWidth = charWidth; + if (cursorOnAscii) { + shadowWidth = itemWidth(); + } else if (editWordState >= EditWordState::WriteNotEdited) { + shadowWidth = itemByteLen * charWidth; + } + shadowCursor.screenPos.setWidth(shadowWidth); painter.drawRect(shadowCursor.screenPos); painter.setPen(Qt::SolidLine); } @@ -1102,20 +1664,26 @@ void HexWidget::drawItemArea(QPainter &painter) fillSelectionBackground(painter); + bool haveEditWord = false; + QRectF editWordRect; + QColor editWordColor; + uint64_t itemAddr = startAddress; for (int line = 0; line < visibleLines; ++line) { itemRect.moveLeft(itemArea.left()); for (int j = 0; j < itemColumns; ++j) { for (int k = 0; k < itemGroupSize && itemAddr <= data->maxIndex(); ++k, itemAddr += itemByteLen) { + itemString = renderItem(itemAddr - startAddress, &itemColor); if (!getFlagsAndComment(itemAddr).isEmpty()) { QColor markerColor(borderColor); markerColor.setAlphaF(0.5); - const auto shape = rangePolygons(itemAddr, itemAddr, false)[0]; painter.setPen(markerColor); - painter.drawPolyline(shape); + for (const auto &shape : rangePolygons(itemAddr, itemAddr, false)) { + painter.drawPolyline(shape); + } } if (selection.contains(itemAddr) && !cursorOnAscii) { itemColor = palette().highlightedText().color(); @@ -1123,19 +1691,53 @@ void HexWidget::drawItemArea(QPainter &painter) if (isItemDifferentAt(itemAddr)) { itemColor.setRgb(diffColor.rgb()); } - painter.setPen(itemColor); - painter.drawText(itemRect, Qt::AlignVCenter, itemString); - itemRect.translate(itemWidth(), 0); - if (cursor.address == itemAddr) { - auto &itemCursor = cursorOnAscii ? shadowCursor : cursor; - itemCursor.cachedChar = itemString.at(0); + + if (editWordState <= EditWordState::WriteNotStarted || cursor.address != itemAddr) { + painter.setPen(itemColor); + painter.drawText(itemRect, Qt::AlignVCenter, itemString); + itemRect.translate(itemWidth(), 0); + if (cursor.address == itemAddr) { + auto &itemCursor = cursorOnAscii ? shadowCursor : cursor; + int itemCharPos = 0; + if (editWordState > EditWordState::Read) { + itemCharPos += itemPrefixLen; + } + if (itemCharPos < itemString.length()) { + itemCursor.cachedChar = itemString.at(itemCharPos); + } else { + itemCursor.cachedChar = ' '; + } + itemCursor.cachedColor = itemColor; + } + } else { + haveEditWord = true; + editWordRect = itemRect; + editWordColor = itemColor; + + auto &itemCursor = cursor; + itemCursor.cachedChar = + editWordPos < editWord.length() ? editWord[editWordPos] : QChar(' '); itemCursor.cachedColor = itemColor; + itemCursor.screenPos.moveTopLeft(itemRect.topLeft()); + itemCursor.screenPos.translate(charWidth * (editWordPos + itemPrefixLen), 0); + + itemRect.translate(itemWidth(), 0); } } itemRect.translate(columnSpacingWidth(), 0); } itemRect.translate(0, lineHeight); } + if (haveEditWord) { + auto length = std::max(itemCharLen, editWord.length()); + auto rect = editWordRect; + rect.setWidth(length * charWidth); + painter.fillRect(rect, backgroundColor); + + painter.setPen(editWordColor); + editWordRect.setWidth(4000); + painter.drawText(editWordRect, Qt::AlignVCenter | Qt::AlignLeft, itemPrefix + editWord); + } painter.setPen(borderColor); @@ -1222,12 +1824,16 @@ QVector HexWidget::rangePolygons(RVA start, RVA last, bool ascii) auto startRect = getRectangle(startOffset); auto endRect = getRectangle(endOffset); + bool startJagged = false; + bool endJagged = false; if (!ascii) { if (int startFraction = startOffset % itemByteLen) { startRect.setLeft(startRect.left() + startFraction * startRect.width() / itemByteLen); + startJagged = true; } if (int endFraction = itemByteLen - 1 - (endOffset % itemByteLen)) { endRect.setRight(endRect.right() - endFraction * endRect.width() / itemByteLen); + endJagged = true; } } if (endOffset - startOffset + 1 <= rowSizeBytes) { @@ -1263,6 +1869,33 @@ QVector HexWidget::rangePolygons(RVA start, RVA last, bool ascii) shape << shape.first(); // close the shape parts.push_back(shape); } + if (!ascii && (startJagged || endJagged) && parts.length() >= 1) { + + QPolygonF top; + top.reserve(3); + top << QPointF(0, 0) << QPointF(charWidth, lineHeight / 3) << QPointF(0, lineHeight / 2); + QPolygonF bottom; + bottom.reserve(3); + bottom << QPointF(0, lineHeight / 2) << QPointF(-charWidth, 2 * lineHeight / 3) + << QPointF(0, lineHeight); + + // small adjustment to make sure that edges don't overlap with rect edges, QPolygonF doesn't + // handle it properly + QPointF adjustment(charWidth / 16, 0); + top.translate(-adjustment); + bottom.translate(adjustment); + + if (startJagged) { + auto movedTop = top.translated(startRect.topLeft()); + auto movedBottom = bottom.translated(startRect.topLeft()); + parts[0] = parts[0].subtracted(movedTop).united(movedBottom); + } + if (endJagged) { + auto movedTop = top.translated(endRect.topRight()); + auto movedBottom = bottom.translated(endRect.topRight()); + parts.last() = parts.last().subtracted(movedBottom).united(movedTop); + } + } return parts; } @@ -1324,18 +1957,44 @@ void HexWidget::updateAreasHeight() asciiArea.setHeight(height); } -void HexWidget::moveCursor(int offset, bool select) +bool HexWidget::moveCursor(int offset, bool select, OverflowMove overflowMove) { - BasicCursor addr = cursor.address; - addr += offset; - if (addr.address > data->maxIndex()) { - addr.address = data->maxIndex(); + BasicCursor addr(cursor.address); + if (overflowMove == OverflowMove::Ignore) { + if (addr.moveChecked(offset)) { + if (addr.address > data->maxIndex()) { + addr.address = data->maxIndex(); + addr.pastEnd = true; + } + setCursorAddr(addr, select); + return true; + } + return false; + } else { + addr += offset; + if (addr.address > data->maxIndex()) { + addr.address = data->maxIndex(); + } + setCursorAddr(addr, select); + return true; + } +} + +void HexWidget::moveCursorKeepEditOffset(int byteOffset, bool select, OverflowMove overflowMove) +{ + int wordOffset = editWordPos; + moveCursor(byteOffset, select, overflowMove); + // preserve position within word when moving vertically in hex or oct modes + if (!cursorOnAscii && !select && wordOffset > 0 && navigationMode == HexNavigationMode::AnyChar + && editWordState > EditWordState::Read) { + startEditWord(); + editWordPos = wordOffset; } - setCursorAddr(addr, select); } void HexWidget::setCursorAddr(BasicCursor addr, bool select) { + finishEditingWord(); if (!select) { bool clearingSelection = !selection.isEmpty(); selection.init(addr); @@ -1345,6 +2004,9 @@ void HexWidget::setCursorAddr(BasicCursor addr, bool select) emit positionChanged(addr.address); cursor.address = addr.address; + if (!cursorOnAscii) { + cursor.address -= cursor.address % itemByteLen; + } /* Pause cursor repainting */ cursorEnabled = false; @@ -1361,7 +2023,9 @@ void HexWidget::setCursorAddr(BasicCursor addr, bool select) addressValue -= (addressValue % itemRowByteLen()); /* FIXME: handling Page Up/Down */ - if (addressValue == startAddress + bytesPerScreen()) { + uint64_t rowAfterVisibleAddress = startAddress + bytesPerScreen(); + if (addressValue == rowAfterVisibleAddress && addressValue > startAddress) { + // when pressing down add only one new row startAddress += itemRowByteLen(); } else { startAddress = addressValue; @@ -1410,6 +2074,10 @@ void HexWidget::updateCursorMeta() point += itemArea.topLeft(); pointAscii += asciiArea.topLeft(); + if (editWordState > EditWordState::Read && !cursorOnAscii) { + point.rx() += itemPrefixLen * charWidth; + } + cursor.screenPos.moveTopLeft(cursorOnAscii ? pointAscii : point); shadowCursor.screenPos.moveTopLeft(cursorOnAscii ? point : pointAscii); } @@ -1419,7 +2087,7 @@ void HexWidget::setCursorOnAscii(bool ascii) cursorOnAscii = ascii; } -const QColor HexWidget::itemColor(uint8_t byte) +QColor HexWidget::itemColor(uint8_t byte) { QColor color(defColor); @@ -1547,7 +2215,7 @@ QString HexWidget::renderItem(int offset, QColor *color) item = QString("%1").arg(itemVal.toLongLong(), itemLen, 10); break; case ItemFormatFloat: - item = QString("%1").arg(itemVal.toDouble(), itemLen); + item = QString("%1").arg(itemVal.toDouble(), itemLen, 'g', itemByteLen == 4 ? 6 : 15); break; } @@ -1588,13 +2256,191 @@ QString HexWidget::getFlagsAndComment(uint64_t address) return metaData; } +bool HexWidget::canKeyboardEdit() +{ + return ioModesController.canWrite() && actionKeyboardEdit->isChecked(); +} + +template +static bool checkRange(BigValue v) +{ + return v >= std::numeric_limits::min() && v <= std::numeric_limits::max(); +} + +template +static bool checkAndWrite(BigInteger value, uint8_t *buf, bool littleEndian) +{ + if (!checkRange(value)) { + return false; + } + if (littleEndian) { + qToLittleEndian((T)value, buf); + } else { + qToBigEndian((T)value, buf); + } + return true; +} + +template +static bool checkAndWriteWithSign(const QVariant &value, uint8_t *buf, bool isSigned, + bool littleEndian) +{ + if (isSigned) { + return checkAndWrite(value.toLongLong(), buf, littleEndian); + } else { + return checkAndWrite(value.toULongLong(), buf, littleEndian); + } +} + +bool HexWidget::parseWord(QString word, uint8_t *buf, size_t bufferSize) const +{ + bool parseOk = false; + if (bufferSize < size_t(itemByteLen)) { + return false; + } + if (itemFormat == ItemFormatFloat) { + if (itemByteLen == 4) { + float value = word.toFloat(&parseOk); + if (!parseOk) { + return false; + } + if (itemBigEndian) { + rz_write_be_float(buf, value); + } else { + rz_write_le_float(buf, value); + } + return true; + } else if (itemByteLen == 8) { + double value = word.toDouble(&parseOk); + if (!parseOk) { + return false; + } + if (itemBigEndian) { + rz_write_be_double(buf, value); + } else { + rz_write_le_double(buf, value); + } + return true; + } + return false; + } else { + QVariant value; + bool isSigned = false; + switch (itemFormat) { + case ItemFormatHex: + value = word.toULongLong(&parseOk, 16); + break; + case ItemFormatOct: + value = word.toULongLong(&parseOk, 8); + break; + case ItemFormatDec: + value = word.toULongLong(&parseOk, 10); + break; + case ItemFormatSignedDec: + isSigned = true; + value = word.toLongLong(&parseOk, 10); + break; + default: + break; + } + if (!parseOk) { + return false; + } + + switch (itemByteLen) { + case 1: + return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); + case 2: + return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); + case 4: + return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); + case 8: + return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); + } + } + return false; +} + +bool HexWidget::flushCurrentlyEditedWord() +{ + if (editWordState < EditWordState::WriteEdited) { + return true; + } + uint8_t buf[16]; + if (parseWord(editWord, buf, sizeof(buf))) { + data->write(buf, cursor.address, itemByteLen); + return true; + } + editWordState = EditWordState::WriteNotEdited; + return false; +} + +bool HexWidget::finishEditingWord(bool force) +{ + if (editWordState == EditWordState::WriteEdited) { + if (!flushCurrentlyEditedWord() && !force) { + qWarning() << "Not a valid number in current format or size" << editWord; + showWarningRect(itemRectangle(cursor.address - startAddress).adjusted(-1, -1, 1, 1)); + return false; + } + } + editWord.clear(); + editWordPos = 0; + editWordState = canKeyboardEdit() ? EditWordState::WriteNotStarted : EditWordState::Read; + navigationMode = defaultNavigationMode(); + return true; +} + +void HexWidget::cancelEditedWord() +{ + editWordPos = 0; + editWordState = canKeyboardEdit() ? EditWordState::WriteNotStarted : EditWordState::Read; + editWord.clear(); + navigationMode = defaultNavigationMode(); + updateCursorMeta(); + viewport()->update(); +} + +void HexWidget::maybeFlushCharEdit() +{ + if (editWordState < EditWordState::WriteEdited) { + return; + } + if ((itemFormat == ItemFormatHex && earlyEditFlush >= EarlyEditFlush::EditNibble) + || (isFixedWidth() && earlyEditFlush >= EarlyEditFlush::EditFixedWidthChar)) { + flushCurrentlyEditedWord(); + if (!flushCurrentlyEditedWord()) { + showWarningRect(itemRectangle(cursor.address - startAddress).adjusted(-1, -1, 1, 1)); + } + } + viewport()->update(); +} + +void HexWidget::startEditWord() +{ + if (!canKeyboardEdit()) { + return; + } + if (editWordState >= EditWordState::WriteNotEdited) { + return; + } + editWordPos = 0; + editWordState = EditWordState::WriteNotEdited; + navigationMode = defaultNavigationMode(); + editWord = renderItem(cursor.address - startAddress).trimmed(); + if (itemPrefixLen > 0) { + editWord = editWord.mid(itemPrefixLen); + } + viewport()->update(); +} + void HexWidget::fetchData() { data.swap(oldData); data->fetch(startAddress, bytesPerScreen()); } -BasicCursor HexWidget::screenPosToAddr(const QPoint &point, bool middle) const +BasicCursor HexWidget::screenPosToAddr(const QPoint &point, bool middle, int *wordOffset) const { QPointF pt = point - itemArea.topLeft(); @@ -1605,9 +2451,21 @@ BasicCursor HexWidget::screenPosToAddr(const QPoint &point, bool middle) const relativeAddress += column * itemGroupByteLen(); pt.rx() -= column * columnExWidth(); auto roundingOffset = middle ? itemWidth() / 2 : 0; - relativeAddress += static_cast((pt.x() + roundingOffset) / itemWidth()) * itemByteLen; + int posInGroup = static_cast((pt.x() + roundingOffset) / itemWidth()); + if (!middle) { + posInGroup = std::min(posInGroup, itemGroupSize - 1); + } + relativeAddress += posInGroup * itemByteLen; + pt.rx() -= posInGroup * itemWidth(); BasicCursor result(startAddress); result += relativeAddress; + + if (!middle && wordOffset != nullptr) { + int charPos = static_cast((pt.x() / charWidth) + 0.5); + charPos -= itemPrefixLen; + charPos = std::max(0, charPos); + *wordOffset = charPos; + } return result; } @@ -1679,3 +2537,17 @@ RVA HexWidget::getLocationAddress() { return !selection.isEmpty() ? selection.start() : cursor.address; } + +void HexWidget::hideWarningRect() +{ + warningRectVisible = false; + viewport()->update(); +} + +void HexWidget::showWarningRect(QRectF rect) +{ + warningRect = rect; + warningRectVisible = true; + warningTimer.start(WARNING_TIME_MS); + viewport()->update(); +} diff --git a/src/widgets/HexWidget.h b/src/widgets/HexWidget.h index b46f0574..103040c2 100644 --- a/src/widgets/HexWidget.h +++ b/src/widgets/HexWidget.h @@ -14,7 +14,7 @@ struct BasicCursor { uint64_t address; bool pastEnd; - BasicCursor(uint64_t pos) : address(pos), pastEnd(false) {} + explicit BasicCursor(uint64_t pos) : address(pos), pastEnd(false) {} BasicCursor() : address(0), pastEnd(false) {} BasicCursor &operator+=(int64_t offset) { @@ -35,6 +35,14 @@ struct BasicCursor *this += int64_t(offset); return *this; } + + bool moveChecked(int offset) + { + auto oldAddress = address; + *this += offset; + return address - oldAddress == uint64_t(offset); + } + BasicCursor &operator+=(uint64_t offset) { if (uint64_t(offset) > (UINT64_MAX - address)) { @@ -46,7 +54,10 @@ struct BasicCursor } return *this; } - bool operator<(const BasicCursor &r) { return address < r.address || (pastEnd < r.pastEnd); } + bool operator<(const BasicCursor &r) const + { + return address < r.address || (pastEnd < r.pastEnd); + } }; struct HexCursor @@ -74,9 +85,10 @@ struct HexCursor class AbstractData { public: - virtual ~AbstractData() {} + virtual ~AbstractData() = default; virtual void fetch(uint64_t addr, int len) = 0; virtual bool copy(void *out, uint64_t adr, size_t len) = 0; + virtual bool write(const uint8_t *in, uint64_t adr, size_t len) = 0; virtual uint64_t maxIndex() = 0; virtual uint64_t minIndex() = 0; }; @@ -86,7 +98,7 @@ class BufferData : public AbstractData public: BufferData() { m_buffer.fill(0, 1); } - BufferData(const QByteArray &buffer) + explicit BufferData(const QByteArray &buffer) { if (buffer.isEmpty()) { m_buffer.fill(0, 1); @@ -95,7 +107,7 @@ public: } } - ~BufferData() override {} + ~BufferData() override = default; void fetch(uint64_t, int) override {} @@ -109,6 +121,16 @@ public: return false; } + bool write(const uint8_t *in, uint64_t addr, size_t len) override + { + if (addr < static_cast(m_buffer.size()) + && (static_cast(m_buffer.size()) - addr) < len) { + memcpy(m_buffer.data() + addr, in, len); + return true; + } + return false; + } + uint64_t maxIndex() override { return m_buffer.size() - 1; } private: @@ -118,8 +140,8 @@ private: class MemoryData : public AbstractData { public: - MemoryData() {} - ~MemoryData() override {} + MemoryData() = default; + ~MemoryData() override = default; static constexpr size_t BLOCK_SIZE = 4096; void fetch(uint64_t address, int length) override @@ -144,10 +166,11 @@ public: bool copy(void *out, uint64_t addr, size_t len) override { - if (addr < m_firstBlockAddr || addr > m_lastValidAddr - || (m_lastValidAddr - addr + 1) - < len /* do not merge with last check to handle overflows */ - || m_blocks.isEmpty()) { + if (addr < m_firstBlockAddr + || addr > m_lastValidAddr + /* do not merge with previous check to handle overflows */ + || (m_lastValidAddr - addr + 1) < len || m_blocks.isEmpty()) { + memset(out, 0xff, len); return false; } @@ -165,9 +188,47 @@ public: return true; } - virtual uint64_t maxIndex() override { return m_lastValidAddr; } + void writeToCache(const uint8_t *in, uint64_t adr, size_t len) + { + if (adr < m_firstBlockAddr) { + uint64_t prefix = m_firstBlockAddr - adr; + if (prefix <= len) { + return; + } + in = in + prefix; + adr += prefix; + len -= prefix; + } + if (adr > m_lastValidAddr) { + return; + } + int offset = (int)(adr - m_firstBlockAddr); + int blockId = offset / BLOCK_SIZE; + int blockOffset = offset % BLOCK_SIZE; + while (len > 0 && blockId < m_blocks.size()) { + size_t l = BLOCK_SIZE - blockOffset; + l = std::min(l, len); + memcpy(m_blocks[blockId].data() + blockOffset, in, l); + len -= l; + blockOffset = 0; + adr += l; + in += l; + blockId += 1; + } + } - virtual uint64_t minIndex() override { return m_firstBlockAddr; } + bool write(const uint8_t *in, uint64_t adr, size_t len) override + { + RzCoreLocked core(Core()); + rz_core_write_at(core, adr, in, len); + writeToCache(in, adr, len); + emit Core()->instructionChanged(adr); + return true; + } + + uint64_t maxIndex() override { return std::numeric_limits::max(); } + + uint64_t minIndex() override { return m_firstBlockAddr; } private: QVector m_blocks; @@ -178,7 +239,11 @@ private: class HexSelection { public: - HexSelection() { m_empty = true; } + HexSelection() + { + m_empty = true; + m_start = m_end = 0; + } inline void init(BasicCursor addr) { @@ -189,7 +254,8 @@ public: void set(uint64_t start, uint64_t end) { m_empty = false; - m_init = m_start = start; + m_init = BasicCursor(start); + m_start = start; m_end = end; } @@ -219,7 +285,7 @@ public: bool contains(uint64_t pos) const { return !m_empty && m_start <= pos && pos <= m_end; } - uint64_t size() + uint64_t size() const { uint64_t size = 0; if (!isEmpty()) @@ -227,9 +293,9 @@ public: return size; } - inline bool isEmpty() { return m_empty; } - inline uint64_t start() { return m_start; } - inline uint64_t end() { return m_end; } + inline bool isEmpty() const { return m_empty; } + inline uint64_t start() const { return m_start; } + inline uint64_t end() const { return m_end; } private: BasicCursor m_init; @@ -244,7 +310,7 @@ class HexWidget : public QScrollArea public: explicit HexWidget(QWidget *parent = nullptr); - ~HexWidget(); + ~HexWidget() override = default; void setMonospaceFont(const QFont &font); @@ -258,10 +324,12 @@ public: ItemFormatFloat }; enum class ColumnMode { Fixed, PowerOf2 }; + enum class EditWordState { Read, WriteNotStarted, WriteNotEdited, WriteEdited }; + enum class HexNavigationMode { Words, WordChar, AnyChar }; void setItemSize(int nbytes); void setItemFormat(ItemFormat format); - void setItemEndianess(bool bigEndian); + void setItemEndianness(bool bigEndian); void setItemGroupSize(int size); /** * @brief Sets line size in bytes. @@ -292,7 +360,7 @@ public slots: void refresh(); void updateColors(); signals: - void selectionChanged(Selection selection); + void selectionChanged(HexWidget::Selection selection); void positionChanged(RVA start); protected: @@ -300,10 +368,12 @@ protected: void resizeEvent(QResizeEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override; + bool event(QEvent *event) override; private slots: void onCursorBlinked(); @@ -311,13 +381,13 @@ private slots: void copy(); void copyAddress(); void onRangeDialogAccepted(); - void on_actionAddComment_triggered(); - void on_actionDeleteComment_triggered(); + void onActionAddCommentTriggered(); + void onActionDeleteCommentTriggered(); // Write command slots void w_writeString(); void w_increaseDecrease(); - void w_writeBytes(); + void w_writeBytes(); void w_writeZeros(); void w_write64(); void w_writeRandom(); @@ -326,6 +396,9 @@ private slots: void w_writeWideString(); void w_writeCString(); + void onKeyboardEditTriggered(bool enabled); + void onKeyboardEditChanged(bool enabled); + private: void updateItemLength(); void updateCounts(); @@ -338,12 +411,15 @@ private: void updateMetrics(); void updateAreasPosition(); void updateAreasHeight(); - void moveCursor(int offset, bool select = false); + enum class OverflowMove { Clamp, Ignore }; + bool moveCursor(int offset, bool select = false, + OverflowMove overflowMove = OverflowMove::Clamp); + void moveCursorKeepEditOffset(int byteOffset, bool select, OverflowMove overflowMove); void setCursorAddr(BasicCursor addr, bool select = false); void updateCursorMeta(); void setCursorOnAscii(bool ascii); bool isItemDifferentAt(uint64_t address); - const QColor itemColor(uint8_t byte); + QColor itemColor(uint8_t byte); QVariant readItem(int offset, QColor *color = nullptr); QString renderItem(int offset, QColor *color = nullptr); QChar renderAscii(int offset, QColor *color = nullptr); @@ -359,12 +435,13 @@ private: /** * @brief Convert mouse position to address. * @param point mouse position in widget - * @param middle start next position from middle of symbol. Use middle=true for vertical cursror + * @param middle start next position from middle of symbol. Use middle=true for vertical cursor * position between symbols, middle=false for insert mode cursor and getting symbol under * cursor. * @return */ - BasicCursor screenPosToAddr(const QPoint &point, bool middle = false) const; + BasicCursor screenPosToAddr(const QPoint &point, bool middle = false, + int *wordOffset = nullptr) const; BasicCursor asciiPosToAddr(const QPoint &point, bool middle = false) const; BasicCursor currentAreaPosToAddr(const QPoint &point, bool middle = false) const; BasicCursor mousePosToAddr(const QPoint &point, bool middle = false) const; @@ -412,6 +489,27 @@ private: inline uint64_t lastVisibleAddr() const { return (startAddress - 1) + bytesPerScreen(); } const QRectF ¤tArea() const { return cursorOnAscii ? asciiArea : itemArea; } + bool isFixedWidth() const; + + bool canKeyboardEdit(); + bool flushCurrentlyEditedWord(); + bool finishEditingWord(bool force = true); + void maybeFlushCharEdit(); + void cancelEditedWord(); + void startEditWord(); + bool validCharForEdit(QChar digit); + void movePrevEditCharAny(); + void typeOverwriteModeChar(QChar c); + HexNavigationMode defaultNavigationMode(); + void refreshWordEditState(); + bool parseWord(QString word, uint8_t *buf, size_t bufferSize) const; + bool handleAsciiWrite(QKeyEvent *event); + bool handleNumberWrite(QKeyEvent *event); + + void writeZeros(uint64_t address, uint64_t length); + + void hideWarningRect(); + void showWarningRect(QRectF rect); bool cursorEnabled; bool cursorOnAscii; @@ -436,14 +534,13 @@ private: ItemFormat itemFormat; bool itemBigEndian; + QString itemPrefix; int visibleLines; uint64_t startAddress; qreal charWidth; - int byteWidth; qreal lineHeight; int addrCharLen; - int addrAreaWidth; QFont monospaceFont; bool showHeader; @@ -460,6 +557,7 @@ private: QColor b0x7fColor; QColor b0xffColor; QColor printableColor; + QColor warningColor; HexdumpRangeDialog rangeDialog; @@ -479,14 +577,30 @@ private: QAction *actionCopyAddress; QAction *actionComment; QAction *actionDeleteComment; - QAction *actionSetFlag; QAction *actionSelectRange; + QAction *actionKeyboardEdit; QList actionsWriteString; QList actionsWriteOther; std::unique_ptr oldData; std::unique_ptr data; IOModesController ioModesController; + + int editWordPos = 0; + QString editWord; + EditWordState editWordState = EditWordState::Read; + HexNavigationMode navigationMode = HexNavigationMode::Words; + enum class EarlyEditFlush { + OnFinish, + EditNibble, + EditFixedWidthChar, + /* AllFormats(not implemented) */ + }; + EarlyEditFlush earlyEditFlush = EarlyEditFlush::EditFixedWidthChar; + + bool warningRectVisible = false; + QRectF warningRect; + QTimer warningTimer; }; #endif // HEXWIDGET_H From d972f86e6f834f4fc7f7693a7063c7e4c57a45d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Mon, 19 Sep 2022 09:47:22 +0200 Subject: [PATCH 32/77] Bump version to 2.1.2 (#3039) --- .appveyor.yml | 2 +- CMakeLists.txt | 2 +- docs/source/conf.py | 2 +- src/re.rizin.cutter.appdata.xml | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 84a3b615..dc3f82d3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,4 +1,4 @@ -version: '2.1.0-git-{build}' +version: '2.1.2-git-{build}' image: 'Visual Studio 2017' clone_depth: 1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b9a9828..e7f950f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ endif() set(CUTTER_VERSION_MAJOR 2) set(CUTTER_VERSION_MINOR 1) -set(CUTTER_VERSION_PATCH 0) +set(CUTTER_VERSION_PATCH 2) set(CUTTER_VERSION_FULL "${CUTTER_VERSION_MAJOR}.${CUTTER_VERSION_MINOR}.${CUTTER_VERSION_PATCH}") diff --git a/docs/source/conf.py b/docs/source/conf.py index b2677d68..640b7f9b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,7 +26,7 @@ author = 'The Cutter Developers' # The short X.Y version version = '2.1' # The full version, including a2lpha/beta/rc tags -release = '2.1.0' +release = '2.1.2' # -- General configuration --------------------------------------------------- diff --git a/src/re.rizin.cutter.appdata.xml b/src/re.rizin.cutter.appdata.xml index c28c481c..e8e0de5e 100644 --- a/src/re.rizin.cutter.appdata.xml +++ b/src/re.rizin.cutter.appdata.xml @@ -25,6 +25,8 @@ xarkes + + From 8ad700a6dc1a4a628f23ebefa2138c5f8df0182d Mon Sep 17 00:00:00 2001 From: Giovanni <561184+wargio@users.noreply.github.com> Date: Wed, 5 Oct 2022 04:35:43 +0200 Subject: [PATCH 33/77] Update rizin (#3044) --- rizin | 2 +- src/core/Cutter.cpp | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/rizin b/rizin index 32ef1713..fbad0b48 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit 32ef171354e2fde6194a354a1a350df357c08afc +Subproject commit fbad0b4859802a62dcc96002c2710e696809a0c3 diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 513806ff..04467ae2 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -3051,12 +3051,8 @@ QList CutterCore::getAllFunctions() FunctionDescription function; function.offset = fcn->addr; function.linearSize = rz_analysis_function_linear_size(fcn); - function.nargs = rz_analysis_var_count(core->analysis, fcn, 'b', 1) - + rz_analysis_var_count(core->analysis, fcn, 'r', 1) - + rz_analysis_var_count(core->analysis, fcn, 's', 1); - function.nlocals = rz_analysis_var_count(core->analysis, fcn, 'b', 0) - + rz_analysis_var_count(core->analysis, fcn, 'r', 0) - + rz_analysis_var_count(core->analysis, fcn, 's', 0); + function.nargs = rz_analysis_var_count_total(fcn, RZ_ANALYSIS_VAR_TYPE_ARGUMENT); + function.nlocals = rz_analysis_var_count_total(fcn, RZ_ANALYSIS_VAR_TYPE_LOCAL); function.nbbs = rz_list_length(fcn->bbs); function.calltype = fcn->cc ? QString::fromUtf8(fcn->cc) : QString(); function.name = fcn->name ? QString::fromUtf8(fcn->name) : QString(); From 0624cd1d19d1a5fb475683b128699383a958691f Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Sun, 23 Oct 2022 13:40:26 +0200 Subject: [PATCH 34/77] Show a nice folder structure in Visual Studio (#2890) --- CMakeLists.txt | 6 ++++++ src/CMakeLists.txt | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e7f950f2..3c615e56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,12 @@ set(CUTTER_VERSION_FULL "${CUTTER_VERSION_MAJOR}.${CUTTER_VERSION_MINOR}.${CUTTE project(Cutter VERSION "${CUTTER_VERSION_FULL}") +# Enable solution folder support +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# Put Qt files in a separate folder +set_property(GLOBAL PROPERTY AUTOGEN_SOURCE_GROUP "Generated Files") + set(CMAKE_CXX_STANDARD 11) include(CutterInstallDirs) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9b606132..e3c2c97d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -442,8 +442,11 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" set_source_files_properties(${BINDINGS_SOURCE} PROPERTIES COMPILE_FLAGS -w) endif() +# Make a source group for Visual Studio +set(CUTTER_SOURCES ${OPTIONS} ${UI_FILES} ${QRC_FILES} ${PLATFORM_RESOURCES} ${SOURCES} ${HEADER_FILES}) +source_group(TREE "${CMAKE_CURRENT_LIST_DIR}" FILES ${CUTTER_SOURCES}) -add_executable(Cutter ${OPTIONS} ${UI_FILES} ${QRC_FILES} ${PLATFORM_RESOURCES} ${SOURCES} ${HEADER_FILES} ${BINDINGS_SOURCE}) +add_executable(Cutter ${CUTTER_SOURCES} ${BINDINGS_SOURCE}) set_target_properties(Cutter PROPERTIES OUTPUT_NAME cutter RUNTIME_OUTPUT_DIRECTORY .. @@ -452,6 +455,9 @@ set_target_properties(Cutter PROPERTIES CXX_VISIBILITY_PRESET hidden) target_compile_definitions(Cutter PRIVATE CUTTER_SOURCE_BUILD) +# Set Cutter as the startup project in Visual Studio +set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Cutter) + set(CUTTER_INCLUDE_DIRECTORIES core widgets common plugins menus .) foreach(_dir ${CUTTER_INCLUDE_DIRECTORIES}) target_include_directories(Cutter PUBLIC From 3224daa0eefef7e2f5e52b49ae6d0a47d33280a3 Mon Sep 17 00:00:00 2001 From: Alex Anderson Date: Sun, 23 Oct 2022 04:43:30 -0700 Subject: [PATCH 35/77] Add case insensitive string search (#2817) --- src/widgets/SearchWidget.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/widgets/SearchWidget.cpp b/src/widgets/SearchWidget.cpp index fc5e5d43..0fcb9f8e 100644 --- a/src/widgets/SearchWidget.cpp +++ b/src/widgets/SearchWidget.cpp @@ -254,6 +254,7 @@ void SearchWidget::refreshSearchspaces() ui->searchspaceCombo->clear(); ui->searchspaceCombo->addItem(tr("asm code"), QVariant("/acj")); ui->searchspaceCombo->addItem(tr("string"), QVariant("/j")); + ui->searchspaceCombo->addItem(tr("string (case insensitive)"), QVariant("/ij")); ui->searchspaceCombo->addItem(tr("hex string"), QVariant("/xj")); ui->searchspaceCombo->addItem(tr("ROP gadgets"), QVariant("/Rj")); ui->searchspaceCombo->addItem(tr("32bit value"), QVariant("/vj")); @@ -301,13 +302,16 @@ void SearchWidget::updatePlaceholderText(int index) case 1: // string ui->filterLineEdit->setPlaceholderText("foobar"); break; - case 2: // hex string + case 2: // string (case insensitive) + ui->filterLineEdit->setPlaceholderText("FooBar"); + break; + case 3: // hex string ui->filterLineEdit->setPlaceholderText("deadbeef"); break; - case 3: // ROP gadgets + case 4: // ROP gadgets ui->filterLineEdit->setPlaceholderText("pop,,pop"); break; - case 4: // 32bit value + case 5: // 32bit value ui->filterLineEdit->setPlaceholderText("0xdeadbeef"); break; default: From 1ac814c1b9394a9c03f76a915b48462d2dff8e51 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Tue, 25 Oct 2022 12:16:48 +0200 Subject: [PATCH 36/77] Fix the padding in SimpleTextGraphView (#3046) --- src/widgets/SimpleTextGraphView.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/SimpleTextGraphView.cpp b/src/widgets/SimpleTextGraphView.cpp index ad4c5deb..feebdd4c 100644 --- a/src/widgets/SimpleTextGraphView.cpp +++ b/src/widgets/SimpleTextGraphView.cpp @@ -119,8 +119,8 @@ void SimpleTextGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block, b p.setPen(palette().color(QPalette::WindowText)); // Render node text - auto x = block.x + padding; - int y = block.y + padding + p.fontMetrics().ascent(); + auto x = block.x + padding / 2; + int y = block.y + padding / 2 + p.fontMetrics().ascent(); p.drawText(QPoint(x, y), content.text); } From ae1e15b7a2ed74b474b1f0fb72acccb25cffdb3a Mon Sep 17 00:00:00 2001 From: Theofilos Pechlivanis Date: Sat, 29 Oct 2022 21:57:30 +0300 Subject: [PATCH 37/77] Keep CallGraph widget active when seeking to other functions --- src/core/MainWindow.cpp | 6 ++++++ src/widgets/CallGraph.cpp | 9 +++++++-- src/widgets/CallGraph.h | 6 ++++-- src/widgets/MemoryDockWidget.h | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 6a840fcc..46c6fac0 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -1086,6 +1086,12 @@ MemoryDockWidget *MainWindow::addNewMemoryWidget(MemoryWidgetType type, RVA addr case MemoryWidgetType::Decompiler: memoryWidget = new DecompilerWidget(this); break; + case MemoryWidgetType::CallGraph: + memoryWidget = new CallGraphWidget(this, false); + break; + case MemoryWidgetType::GlobalCallGraph: + memoryWidget = new CallGraphWidget(this, true); + break; } auto seekable = memoryWidget->getSeekable(); seekable->setSynchronization(synchronized); diff --git a/src/widgets/CallGraph.cpp b/src/widgets/CallGraph.cpp index 8c3d7e4a..64736573 100644 --- a/src/widgets/CallGraph.cpp +++ b/src/widgets/CallGraph.cpp @@ -7,9 +7,9 @@ #include CallGraphWidget::CallGraphWidget(MainWindow *main, bool global) - : AddressableDockWidget(main), graphView(new CallGraphView(this, main, global)), global(global) + : MemoryDockWidget(MemoryWidgetType::CallGraph, main), graphView(new CallGraphView(this, main, global)), global(global) { - setObjectName(main->getUniqueObjectName("CallGraphWidget")); + setObjectName(main ? main->getUniqueObjectName(getWidgetType()) : getWidgetType()); this->setWindowTitle(getWindowTitle()); connect(seekable, &CutterSeekable::seekableSeekChanged, this, &CallGraphWidget::onSeekChanged); @@ -23,6 +23,11 @@ QString CallGraphWidget::getWindowTitle() const return global ? tr("Global Callgraph") : tr("Callgraph"); } +QString CallGraphWidget::getWidgetType() const +{ + return global ? tr("GlobalCallgraph") : tr("Callgraph"); +} + void CallGraphWidget::onSeekChanged(RVA address) { if (auto function = Core()->functionIn(address)) { diff --git a/src/widgets/CallGraph.h b/src/widgets/CallGraph.h index 2d348266..2c6e0a96 100644 --- a/src/widgets/CallGraph.h +++ b/src/widgets/CallGraph.h @@ -2,7 +2,7 @@ #define CALL_GRAPH_WIDGET_H #include "core/Cutter.h" -#include "AddressableDockWidget.h" +#include "MemoryDockWidget.h" #include "widgets/SimpleTextGraphView.h" #include "common/RefreshDeferrer.h" @@ -30,7 +30,7 @@ private: RVA lastLoadedAddress = RVA_INVALID; }; -class CallGraphWidget : public AddressableDockWidget +class CallGraphWidget : public MemoryDockWidget { Q_OBJECT @@ -38,6 +38,8 @@ public: explicit CallGraphWidget(MainWindow *main, bool global); ~CallGraphWidget(); + QString getWidgetType() const; + protected: QString getWindowTitle() const override; diff --git a/src/widgets/MemoryDockWidget.h b/src/widgets/MemoryDockWidget.h index 51d0ca88..0b1a5952 100644 --- a/src/widgets/MemoryDockWidget.h +++ b/src/widgets/MemoryDockWidget.h @@ -7,7 +7,7 @@ #include /* Disassembly/Graph/Hexdump/Decompiler view priority */ -enum class MemoryWidgetType { Disassembly, Graph, Hexdump, Decompiler }; +enum class MemoryWidgetType { Disassembly, Graph, Hexdump, Decompiler, CallGraph, GlobalCallGraph }; class CUTTER_EXPORT MemoryDockWidget : public AddressableDockWidget { From 22e8bf23819a3305292331aa9e030d18c7af81c2 Mon Sep 17 00:00:00 2001 From: Theofilos Pechlivanis <49034471+theopechli@users.noreply.github.com> Date: Tue, 1 Nov 2022 22:01:56 +0200 Subject: [PATCH 38/77] Refresh Call Graph when renaming functions (#3049) --- src/widgets/CallGraph.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widgets/CallGraph.cpp b/src/widgets/CallGraph.cpp index 64736573..001844d0 100644 --- a/src/widgets/CallGraph.cpp +++ b/src/widgets/CallGraph.cpp @@ -42,6 +42,7 @@ CallGraphView::CallGraphView(CutterDockWidget *parent, MainWindow *main, bool gl refreshDeferrer.registerFor(parent); connect(&refreshDeferrer, &RefreshDeferrer::refreshNow, this, &CallGraphView::refreshView); connect(Core(), &CutterCore::refreshAll, this, &SimpleTextGraphView::refreshView); + connect(Core(), &CutterCore::functionRenamed, this, &CallGraphView::refreshView); } void CallGraphView::showExportDialog() From e7c9ab515e71dd240d1be9c9dee0ab69f8af176f Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Wed, 2 Nov 2022 18:14:23 +0800 Subject: [PATCH 39/77] Update Rizin to the latest `dev` (#3050) * Update Rizin * Remove rz-agent from CMake scripts --- cmake/BundledRizin.cmake | 2 +- rizin | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/BundledRizin.cmake b/cmake/BundledRizin.cmake index 0a3c4491..c6982838 100644 --- a/cmake/BundledRizin.cmake +++ b/cmake/BundledRizin.cmake @@ -63,7 +63,7 @@ set (RZ_LIBS rz_core rz_config rz_cons rz_io rz_util rz_flag rz_asm rz_debug rz_search rz_syscall rz_socket rz_magic rz_crypto rz_type rz_diff rz_sign rz_demangler) set (RZ_EXTRA_LIBS rz_main) -set (RZ_BIN rz-agent rz-bin rizin rz-diff rz-find rz-gg rz-hash rz-run rz-asm rz-ax) +set (RZ_BIN rz-bin rizin rz-diff rz-find rz-gg rz-hash rz-run rz-asm rz-ax) target_link_libraries(Rizin INTERFACE ${RZ_LIBS}) diff --git a/rizin b/rizin index fbad0b48..b59d9a03 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit fbad0b4859802a62dcc96002c2710e696809a0c3 +Subproject commit b59d9a03b2618d7209925da2a7873b40972f42a4 From e56a0b55812cd8d8d30cd74f86ca1a4c5caf36f8 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Wed, 2 Nov 2022 19:13:57 +0800 Subject: [PATCH 40/77] Fix GitHub Actions warnings (#3051) --- .github/workflows/ci.yml | 6 +++--- .github/workflows/coverity-scan.yml | 8 ++++---- .github/workflows/docs.yml | 4 ++-- .github/workflows/linter.yml | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edd72529..36ff9aa6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: # Prevent one job from pausing the rest fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive persist-credentials: false @@ -79,7 +79,7 @@ jobs: then sudo apt-get install qt5-default libqt5svg5-dev qttools5-dev qttools5-dev-tools fi - - uses: actions/setup-python@v1 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: homebrew dependencies @@ -276,7 +276,7 @@ jobs: echo PACKAGE_NAME=Cutter-${PACKAGE_ID}-src.tar.gz >> $GITHUB_ENV echo PACKAGE_PATH=Cutter-${PACKAGE_ID}-src.tar.gz >> $GITHUB_ENV echo UPLOAD_ASSET_TYPE=application/gzip >> $GITHUB_ENV - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 if: env.PACKAGE_NAME != null with: name: ${{ env.PACKAGE_NAME }} diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity-scan.yml index 4672244b..66742428 100644 --- a/.github/workflows/coverity-scan.yml +++ b/.github/workflows/coverity-scan.yml @@ -7,16 +7,16 @@ jobs: latest: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive - - uses: actions/setup-python@v1 + - uses: actions/setup-python@v4 with: - python-version: 3.7.x + python-version: 3.9.x - name: Download Coverity Build Tool run: | - wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=$TOKEN&project=radareorg%2Fcutter" -O cov-analysis-linux64.tar.gz + wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=$TOKEN&project=rizinorg%2Fcutter" -O cov-analysis-linux64.tar.gz mkdir cov-analysis-linux64 tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64 env: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a423c001..09929175 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -7,9 +7,9 @@ on: jobs: deploy: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive - name: install dependencies diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index dfe70b68..c9488b8e 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -16,7 +16,7 @@ jobs: outputs: clang-format: ${{ steps.filter.outputs.clang-format }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dorny/paths-filter@v2 id: filter with: @@ -33,7 +33,7 @@ jobs: if: ${{ needs.changes.outputs.clang-format == 'true' }} steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install wget run: sudo apt --assume-yes install wget From dda1ece261e8c20c612ca449e96ada4644b89cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 4 Nov 2022 15:17:23 +0100 Subject: [PATCH 41/77] Remove use of deprecated rz_analysis_var_all_list() (#3052) --- src/core/Cutter.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 04467ae2..696cea7f 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -3966,8 +3966,7 @@ QList CutterCore::getXRefsForVariable(QString variableName, boo const auto typ = findWrites ? RZ_ANALYSIS_VAR_ACCESS_TYPE_WRITE : RZ_ANALYSIS_VAR_ACCESS_TYPE_READ; QList xrefList = QList(); - RzList *vars = rz_analysis_var_all_list(core->analysis, fcn); - for (const auto &v : CutterRzList(vars)) { + for (const auto &v : CutterPVector(&fcn->vars)) { if (variableName != v->name) { continue; } From abfcad65848af5af7c448b866ea65563fec5fe00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 5 Nov 2022 00:13:02 +0100 Subject: [PATCH 42/77] Update rizin with new var counting functions (#3053) --- rizin | 2 +- src/core/Cutter.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rizin b/rizin index b59d9a03..3f077bce 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit b59d9a03b2618d7209925da2a7873b40972f42a4 +Subproject commit 3f077bce4a0a7c6323b31d06c6a3a631a988fa5f diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 696cea7f..ce9d4cdb 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -3051,8 +3051,8 @@ QList CutterCore::getAllFunctions() FunctionDescription function; function.offset = fcn->addr; function.linearSize = rz_analysis_function_linear_size(fcn); - function.nargs = rz_analysis_var_count_total(fcn, RZ_ANALYSIS_VAR_TYPE_ARGUMENT); - function.nlocals = rz_analysis_var_count_total(fcn, RZ_ANALYSIS_VAR_TYPE_LOCAL); + function.nargs = rz_analysis_arg_count(fcn); + function.nlocals = rz_analysis_var_local_count(fcn); function.nbbs = rz_list_length(fcn->bbs); function.calltype = fcn->cc ? QString::fromUtf8(fcn->cc) : QString(); function.name = fcn->name ? QString::fromUtf8(fcn->name) : QString(); From 22613b52ede90750bbc68f5431a88380d4945505 Mon Sep 17 00:00:00 2001 From: Paula Date: Sun, 6 Nov 2022 07:32:30 +0100 Subject: [PATCH 43/77] Adding some functions to "banned" (#2816) --- src/widgets/ImportsWidget.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/widgets/ImportsWidget.h b/src/widgets/ImportsWidget.h index 85819bf5..9972b3a6 100644 --- a/src/widgets/ImportsWidget.h +++ b/src/widgets/ImportsWidget.h @@ -28,6 +28,7 @@ private: const QRegularExpression banned = QRegularExpression( QStringLiteral("\\A(\\w\\.)*(system|strcpy|strcpyA|strcpyW|wcscpy|_tcscpy|_mbscpy|" "StrCpy|StrCpyA|StrCpyW|lstrcpy|lstrcpyA|lstrcpyW" + "DCIEnum|DCIOpenProvider|DCISendCommand|DCIBeginAccess" "|_tccpy|_mbccpy|_ftcscpy|strcat|strcatA|strcatW|wcscat|_tcscat|_mbscat|" "StrCat|StrCatA|StrCatW|lstrcat|lstrcatA|" "lstrcatW|StrCatBuff|StrCatBuffA|StrCatBuffW|StrCatChainW|_tccat|_" @@ -51,7 +52,7 @@ private: "ui64tow|_ultoa|_ultot|_ultow|CharToOem|CharToOemA|CharToOemW|" "OemToChar|OemToCharA|OemToCharW|CharToOemBuffA|CharToOemBuffW|alloca|_" "alloca|strlen|wcslen|_mbslen|_mbstrlen|StrLen|lstrlen|" - "ChangeWindowMessageFilter)\\z")); + "ChangeWindowMessageFilter|ChangeWindowMessageFilterEx)\\z")); QList imports; public: From d58edca0c858e96fd75ae22627e86eaec09de588 Mon Sep 17 00:00:00 2001 From: Giovanni <561184+wargio@users.noreply.github.com> Date: Fri, 25 Nov 2022 15:53:10 +0100 Subject: [PATCH 44/77] Fix wrong tg link in new issue (#3057) --- .github/ISSUE_TEMPLATE/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index b54d7076..acc7e264 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,9 +2,9 @@ blank_issues_enabled: false contact_links: - name: Questions Telegram - url: https://t.me/r2cutter + url: https://t.me/cutter_re about: Please ask questions about Cutter here or one of the other community channels, not in the issue tracker. - name: Questions IRC url: https://web.libera.chat/#cutter - about: "#cutter on https://web.libera.chat/" \ No newline at end of file + about: "#cutter on https://web.libera.chat/" From d47eb1c41fed647e453f81aa6e38961440a61084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Mon, 2 Jan 2023 13:29:41 +0100 Subject: [PATCH 45/77] Update to new variable storage in Rizin (#3062) --- rizin | 2 +- src/core/Cutter.cpp | 13 +------------ src/core/CutterDescriptions.h | 3 +-- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/rizin b/rizin index 3f077bce..d9950f74 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit 3f077bce4a0a7c6323b31d06c6a3a631a988fa5f +Subproject commit d9950f74792c1dfb565ac491cc7ef706b80e6044 diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index ce9d4cdb..b21a1e03 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -1793,18 +1793,7 @@ QList CutterCore::getVariables(RVA at) } for (auto var : CutterPVector(&fcn->vars)) { VariableDescription desc; - switch (var->kind) { - case RZ_ANALYSIS_VAR_KIND_BPV: - desc.refType = VariableDescription::RefType::BP; - break; - case RZ_ANALYSIS_VAR_KIND_SPV: - desc.refType = VariableDescription::RefType::SP; - break; - case RZ_ANALYSIS_VAR_KIND_REG: - default: - desc.refType = VariableDescription::RefType::Reg; - break; - } + desc.storageType = var->storage.type; if (!var->name || !var->type) { continue; } diff --git a/src/core/CutterDescriptions.h b/src/core/CutterDescriptions.h index a56f7186..aed9b508 100644 --- a/src/core/CutterDescriptions.h +++ b/src/core/CutterDescriptions.h @@ -355,8 +355,7 @@ struct RefDescription struct VariableDescription { - enum class RefType { SP, BP, Reg }; - RefType refType; + RzAnalysisVarStorageType storageType; QString name; QString type; }; From 2d7fd02a62dd3405d5405a997a8263c6e7706d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Wed, 4 Jan 2023 18:23:07 +0100 Subject: [PATCH 46/77] Unify parents of dialogs shown by DecompilerContextMenu (#3066) When triggered through a keyboard shortcut, the dialogs shown here would be positioned at the last position of the context menu or (0, 0), which is not desired. Using a currently shown widget as the parent fixes this. --- src/menus/DecompilerContextMenu.cpp | 29 ++++++++++++++++++----------- src/menus/DecompilerContextMenu.h | 6 ++++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/menus/DecompilerContextMenu.cpp b/src/menus/DecompilerContextMenu.cpp index 7113ec68..2dd6907a 100644 --- a/src/menus/DecompilerContextMenu.cpp +++ b/src/menus/DecompilerContextMenu.cpp @@ -69,6 +69,11 @@ DecompilerContextMenu::DecompilerContextMenu(QWidget *parent, MainWindow *mainWi DecompilerContextMenu::~DecompilerContextMenu() {} +QWidget *DecompilerContextMenu::parentForDialog() +{ + return parentWidget(); +} + void DecompilerContextMenu::setAnnotationHere(RzCodeAnnotation *annotation) { annotationHere = annotation; @@ -404,14 +409,15 @@ void DecompilerContextMenu::actionRenameThingHereTriggered() RzAnalysisFunction *func = Core()->functionAt(func_addr); if (func == NULL) { QString function_name = QInputDialog::getText( - this, tr("Define this function at %2").arg(RzAddressString(func_addr)), + parentForDialog(), + tr("Define this function at %2").arg(RzAddressString(func_addr)), tr("Function name:"), QLineEdit::Normal, currentName, &ok); if (ok && !function_name.isEmpty()) { Core()->createFunctionAt(func_addr, function_name); } } else { QString newName = QInputDialog::getText( - this->mainWindow, tr("Rename function %2").arg(currentName), + parentForDialog(), tr("Rename function %2").arg(currentName), tr("Function name:"), QLineEdit::Normal, currentName, &ok); if (ok && !newName.isEmpty()) { Core()->renameFunction(func_addr, newName); @@ -421,16 +427,16 @@ void DecompilerContextMenu::actionRenameThingHereTriggered() RVA var_addr = annotationHere->reference.offset; RzFlagItem *flagDetails = rz_flag_get_i(core->flags, var_addr); if (flagDetails) { - QString newName = QInputDialog::getText(this, tr("Rename %2").arg(flagDetails->name), - tr("Enter name"), QLineEdit::Normal, - flagDetails->name, &ok); + QString newName = QInputDialog::getText( + parentForDialog(), tr("Rename %2").arg(flagDetails->name), tr("Enter name"), + QLineEdit::Normal, flagDetails->name, &ok); if (ok && !newName.isEmpty()) { Core()->renameFlag(flagDetails->name, newName); } } else { QString newName = QInputDialog::getText( - this, tr("Add name to %2").arg(curHighlightedWord), tr("Enter name"), - QLineEdit::Normal, curHighlightedWord, &ok); + parentForDialog(), tr("Add name to %2").arg(curHighlightedWord), + tr("Enter name"), QLineEdit::Normal, curHighlightedWord, &ok); if (ok && !newName.isEmpty()) { Core()->addFlag(var_addr, newName, 1); } @@ -439,14 +445,14 @@ void DecompilerContextMenu::actionRenameThingHereTriggered() if (!variablePresentInRizin()) { // Show can't rename this variable dialog QMessageBox::critical( - this, + parentForDialog(), tr("Rename local variable %1").arg(QString(annotationHere->variable.name)), tr("Can't rename this variable. " "Only local variables defined in disassembly can be renamed.")); return; } QString oldName(annotationHere->variable.name); - QString newName = QInputDialog::getText(this, tr("Rename %2").arg(oldName), + QString newName = QInputDialog::getText(parentForDialog(), tr("Rename %2").arg(oldName), tr("Enter name"), QLineEdit::Normal, oldName, &ok); if (ok && !newName.isEmpty()) { Core()->renameFunctionVariable(newName, oldName, decompiledFunctionAddress); @@ -465,13 +471,14 @@ void DecompilerContextMenu::actionEditFunctionVariablesTriggered() return; } else if (!variablePresentInRizin()) { QMessageBox::critical( - this, tr("Edit local variable %1").arg(QString(annotationHere->variable.name)), + parentForDialog(), + tr("Edit local variable %1").arg(QString(annotationHere->variable.name)), tr("Can't edit this variable. " "Only local variables defined in disassembly can be edited.")); return; } EditVariablesDialog dialog(decompiledFunctionAddress, QString(annotationHere->variable.name), - this); + parentForDialog()); dialog.exec(); } diff --git a/src/menus/DecompilerContextMenu.h b/src/menus/DecompilerContextMenu.h index 7a1d5def..c71a2d27 100644 --- a/src/menus/DecompilerContextMenu.h +++ b/src/menus/DecompilerContextMenu.h @@ -111,6 +111,12 @@ private: QAction actionSetPC; // Private Functions + + /** + * \return widget that should be used as parent for presenting dialogs + */ + QWidget *parentForDialog(); + /** * @brief Sets the shortcut context in all the actions contained * in the specified QMenu to Qt::WidgetWithChildrenShortcut. From ad82407c2c1bc82b89a55606b9894def879626d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 7 Jan 2023 20:05:14 +0100 Subject: [PATCH 47/77] Fix changing the CC of a function (#3067) The QByteArray must be kept alive as long as its contens are used. Also in this function, Core()->renameFunction() is not used to avoid sending multiple signals for a single edit action. --- src/menus/DisassemblyContextMenu.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index 50277c7d..f8604e6b 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -1023,18 +1023,15 @@ void DisassemblyContextMenu::on_actionEditFunction_triggered() if (dialog.exec()) { QString new_name = dialog.getNameText(); - Core()->renameFunction(fcn->addr, new_name); + rz_core_analysis_function_rename(core, fcn->addr, new_name.toStdString().c_str()); QString new_start_addr = dialog.getStartAddrText(); fcn->addr = Core()->math(new_start_addr); QString new_stack_size = dialog.getStackSizeText(); fcn->stack = int(Core()->math(new_stack_size)); - const char *ccSelected = dialog.getCallConSelected().toUtf8().constData(); - if (RZ_STR_ISEMPTY(ccSelected)) { - return; - } - if (rz_analysis_cc_exist(core->analysis, ccSelected)) { - fcn->cc = rz_str_constpool_get(&core->analysis->constpool, ccSelected); + QByteArray newCC = dialog.getCallConSelected().toUtf8(); + if (!newCC.isEmpty() && rz_analysis_cc_exist(core->analysis, newCC.constData())) { + fcn->cc = rz_str_constpool_get(&core->analysis->constpool, newCC.constData()); } emit Core()->functionsChanged(); From 3d49c4b65a952196e0df0f1e1ced9079b7523e38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 7 Jan 2023 20:05:41 +0100 Subject: [PATCH 48/77] Fix parsing of new types from C (#3068) In the TypesWidget, right-click+Load New Types was opening the dialog for editing an existing type instead of creating a new one. This would either result in an error (for atomic types) or the old type being deleted on success. --- src/dialogs/TypesInteractionDialog.cpp | 16 +++++++++++++--- src/widgets/TypesWidget.cpp | 1 - 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/dialogs/TypesInteractionDialog.cpp b/src/dialogs/TypesInteractionDialog.cpp index eb2c2284..be363a30 100644 --- a/src/dialogs/TypesInteractionDialog.cpp +++ b/src/dialogs/TypesInteractionDialog.cpp @@ -22,7 +22,6 @@ TypesInteractionDialog::TypesInteractionDialog(QWidget *parent, bool readOnly) syntaxHighLighter = Config()->createSyntaxHighlighter(ui->plainTextEdit->document()); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui->plainTextEdit->setReadOnly(readOnly); - this->typeName = ""; } TypesInteractionDialog::~TypesInteractionDialog() {} @@ -64,10 +63,21 @@ void TypesInteractionDialog::done(int r) { if (r == QDialog::Accepted) { RzCoreLocked core(Core()); - bool edited = rz_type_db_edit_base_type( + bool success; + if (!typeName.isEmpty()) { + success = rz_type_db_edit_base_type( core->analysis->typedb, this->typeName.toUtf8().constData(), ui->plainTextEdit->toPlainText().toUtf8().constData()); - if (edited) { + } else { + char *error_msg = NULL; + success = rz_type_parse_string_stateless(core->analysis->typedb->parser, + ui->plainTextEdit->toPlainText().toUtf8().constData(), &error_msg) == 0; + if (error_msg) { + RZ_LOG_ERROR("%s\n", error_msg); + rz_mem_free(error_msg); + } + } + if (success) { emit newTypesLoaded(); QDialog::done(r); return; diff --git a/src/widgets/TypesWidget.cpp b/src/widgets/TypesWidget.cpp index ebdeace1..3891314e 100644 --- a/src/widgets/TypesWidget.cpp +++ b/src/widgets/TypesWidget.cpp @@ -293,7 +293,6 @@ void TypesWidget::on_actionLoad_New_Types_triggered() TypesInteractionDialog dialog(this); connect(&dialog, &TypesInteractionDialog::newTypesLoaded, this, &TypesWidget::refreshTypes); dialog.setWindowTitle(tr("Load New Types")); - dialog.setTypeName(t.type); dialog.exec(); } From 34018519defc3a7eae46473d7e20dff96b612c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sun, 8 Jan 2023 12:04:36 +0100 Subject: [PATCH 49/77] Cleanup and fix macOS CI (#3070) 2to3 has some suffix now and workarounds are no longer necessary. --- .github/workflows/ci.yml | 5 +---- scripts/Brewfile | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36ff9aa6..86a9ce1d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,11 +86,8 @@ jobs: if: contains(matrix.os, 'macos') run: | cd scripts - rm '/usr/local/bin/2to3' # symlink to some kind of existing python2.7 installation conflicts with brew python3 which gets installed as indirect dependency - brew update --preinstall # temporary workaround for https://github.com/Homebrew/homebrew-bundle/issues/751 + rm /usr/local/bin/2to3* # symlink to some kind of existing python2.7 installation conflicts with brew python3 which gets installed as indirect dependency brew bundle - brew install coreutils - brew install pkg-config - name: py dependencies run: | python3 -m pip install -U pip==21.3.1 diff --git a/scripts/Brewfile b/scripts/Brewfile index dc80bdbc..fb6bc93c 100644 --- a/scripts/Brewfile +++ b/scripts/Brewfile @@ -3,4 +3,6 @@ brew "ccache" brew "openssl" brew "xz" brew "llvm" -brew "meson" \ No newline at end of file +brew "meson" +brew "coreutils" +brew "pkg-config" From a82d4402f8390b69d7e5e40eb65a282d36775cb7 Mon Sep 17 00:00:00 2001 From: karliss Date: Tue, 10 Jan 2023 03:45:47 +0200 Subject: [PATCH 50/77] Store action data as QString. (#3073) Fixes compilation error in newer QT versions, and also prevents risk of potential memory issues. --- src/menus/DisassemblyContextMenu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index f8604e6b..ab54328d 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -524,7 +524,7 @@ void DisassemblyContextMenu::aboutToShowSlot() continue; } structureOffsetMenu->addAction("[" + memBaseReg + " + " + ty->path + "]") - ->setData(ty->path); + ->setData(QString(ty->path)); } rz_list_free(typeoffs); } From 93acec9682e8e20c5e570b7ddc3ca2cf94ebcfb2 Mon Sep 17 00:00:00 2001 From: Tristan <35229978+TristanCrawford@users.noreply.github.com> Date: Wed, 11 Jan 2023 03:38:23 -0600 Subject: [PATCH 51/77] Add text debounce to QuickFilterView & ComboQuickFilterView (#3072) --- src/widgets/ComboQuickFilterView.cpp | 8 +++++++- src/widgets/ComboQuickFilterView.h | 2 ++ src/widgets/QuickFilterView.cpp | 8 +++++++- src/widgets/QuickFilterView.h | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/widgets/ComboQuickFilterView.cpp b/src/widgets/ComboQuickFilterView.cpp index 0f9d827c..a7bcef60 100644 --- a/src/widgets/ComboQuickFilterView.cpp +++ b/src/widgets/ComboQuickFilterView.cpp @@ -6,8 +6,14 @@ ComboQuickFilterView::ComboQuickFilterView(QWidget *parent) { ui->setupUi(this); + debounceTimer = new QTimer(this); + debounceTimer->setSingleShot(true); + + connect(debounceTimer, &QTimer::timeout, this, + [this]() { emit filterTextChanged(ui->lineEdit->text()); }); + connect(ui->lineEdit, &QLineEdit::textChanged, this, - [this](const QString &text) { emit filterTextChanged(text); }); + [this](const QString &text) { debounceTimer->start(150); }); } ComboQuickFilterView::~ComboQuickFilterView() diff --git a/src/widgets/ComboQuickFilterView.h b/src/widgets/ComboQuickFilterView.h index ba913649..5dc217dc 100644 --- a/src/widgets/ComboQuickFilterView.h +++ b/src/widgets/ComboQuickFilterView.h @@ -5,6 +5,7 @@ #include #include +#include namespace Ui { class ComboQuickFilterView; @@ -32,6 +33,7 @@ signals: private: Ui::ComboQuickFilterView *ui; + QTimer *debounceTimer; }; #endif // COMBOQUICKFILTERVIEW_H diff --git a/src/widgets/QuickFilterView.cpp b/src/widgets/QuickFilterView.cpp index f6bb88a1..1f04c5b9 100644 --- a/src/widgets/QuickFilterView.cpp +++ b/src/widgets/QuickFilterView.cpp @@ -7,10 +7,16 @@ QuickFilterView::QuickFilterView(QWidget *parent, bool defaultOn) { ui->setupUi(this); + debounceTimer = new QTimer(this); + debounceTimer->setSingleShot(true); + connect(ui->closeFilterButton, &QAbstractButton::clicked, this, &QuickFilterView::closeFilter); + connect(debounceTimer, &QTimer::timeout, this, + [this]() { emit filterTextChanged(ui->filterLineEdit->text()); }); + connect(ui->filterLineEdit, &QLineEdit::textChanged, this, - [this](const QString &text) { emit filterTextChanged(text); }); + [this](const QString &text) { debounceTimer->start(150); }); if (!defaultOn) { closeFilter(); diff --git a/src/widgets/QuickFilterView.h b/src/widgets/QuickFilterView.h index 7c92c3a3..b6cbb15b 100644 --- a/src/widgets/QuickFilterView.h +++ b/src/widgets/QuickFilterView.h @@ -7,6 +7,7 @@ #include #include +#include namespace Ui { class QuickFilterView; @@ -31,6 +32,7 @@ signals: private: std::unique_ptr ui; + QTimer *debounceTimer; }; #endif // QUICKFILTERVIEW_H From 54cd0f1a428d9b55426eecb519b9c1610235dc68 Mon Sep 17 00:00:00 2001 From: Semnodime Date: Wed, 11 Jan 2023 10:39:43 +0100 Subject: [PATCH 52/77] Fix `fo`/`fortune` command in python sample (#3076, Fix #3075) --- src/plugins/sample-python/sample_python.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/sample-python/sample_python.py b/src/plugins/sample-python/sample_python.py index 6267b900..3be1ad76 100644 --- a/src/plugins/sample-python/sample_python.py +++ b/src/plugins/sample-python/sample_python.py @@ -36,7 +36,7 @@ class FortuneWidget(cutter.CutterDockWidget): self.show() def generate_fortune(self): - fortune = cutter.cmd("fo").replace("\n", "") + fortune = cutter.cmd("fortune").replace("\n", "") res = cutter.core().cmdRaw(f"?E {fortune}") self.text.setText(res) @@ -44,7 +44,7 @@ class FortuneWidget(cutter.CutterDockWidget): class CutterSamplePlugin(cutter.CutterPlugin): name = "Sample Plugin" description = "A sample plugin written in python." - version = "1.1" + version = "1.2" author = "Cutter developers" # Override CutterPlugin methods From c5f6cc0c3f1a3cb33ae7e53d2708ed7baf5e2703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Wed, 11 Jan 2023 12:10:39 +0100 Subject: [PATCH 53/77] Update gha get-release action to fix it on new macOS --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86a9ce1d..04c93efa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -281,7 +281,7 @@ jobs: - name: Get release if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') id: get_release - uses: karliss/get-release@23b8b7144dd5b0c9d6942b2fb78bd9ae71546d03 + uses: rizinorg/gha-get-release@c8074dd5d13ddd0a194d8c9205a1466973c7dc0d env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload release assets From 4e0e0000e15d173608b61f5df2ed055f9f6c8650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Mon, 9 Jan 2023 18:19:48 +0100 Subject: [PATCH 54/77] Remove Breakpad Crash Reporting Crash dumps generated with breakpad were not made use of to an extent that would justify the extra maintenance overhead that it comes with. As Windows builds have recently been broken by it, now is a good time to retire it. --- .appveyor.yml | 2 - .github/workflows/ci.yml | 8 - .woodpecker/macos-arm64.yml | 3 - CMakeLists.txt | 2 - cmake/FindBreakpad.cmake | 65 - dist/CMakeLists.txt | 4 - dist/MacOSSetupBundle.cmake.in | 8 - docs/source/building.rst | 26 +- .../code/crash-handling-system.rst | 32 - docs/source/images/crash-dialog.png | Bin 29528 -> 0 bytes docs/source/images/success-dump-dialog.png | Bin 14629 -> 0 bytes scripts/breakpad_client.gyp | 34 - scripts/breakpad_extract_symbols_appimage.py | 55 - scripts/breakpad_macos.patch | 1377 ----------------- scripts/prepare_breakpad.bat | 33 - scripts/prepare_breakpad_linux.sh | 11 - scripts/prepare_breakpad_macos.sh | 24 - src/CMakeLists.txt | 18 - src/CutterApplication.cpp | 1 - src/Main.cpp | 12 - src/common/CrashHandler.cpp | 163 -- src/common/CrashHandler.h | 16 - 22 files changed, 1 insertion(+), 1893 deletions(-) delete mode 100644 cmake/FindBreakpad.cmake delete mode 100644 docs/source/contributing/code/crash-handling-system.rst delete mode 100644 docs/source/images/crash-dialog.png delete mode 100644 docs/source/images/success-dump-dialog.png delete mode 100644 scripts/breakpad_client.gyp delete mode 100644 scripts/breakpad_extract_symbols_appimage.py delete mode 100644 scripts/breakpad_macos.patch delete mode 100644 scripts/prepare_breakpad.bat delete mode 100755 scripts/prepare_breakpad_linux.sh delete mode 100755 scripts/prepare_breakpad_macos.sh delete mode 100644 src/common/CrashHandler.cpp delete mode 100644 src/common/CrashHandler.h diff --git a/.appveyor.yml b/.appveyor.yml index dc3f82d3..375d116e 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -30,7 +30,6 @@ install: before_build: - cmd: git submodule update --init --recursive - - scripts\prepare_breakpad.bat # Build config build_script: @@ -48,7 +47,6 @@ build_script: -DCUTTER_PACKAGE_RZ_GHIDRA=ON -DCUTTER_PACKAGE_JSDEC=ON -DCUTTER_ENABLE_DEPENDENCY_DOWNLOADS=ON - -DCUTTER_ENABLE_CRASH_REPORTS=ON -DCMAKE_PREFIX_PATH=%CUTTER_DEPS%\\pyside -DCPACK_PACKAGE_FILE_NAME=%PACKAGE_NAME% -G Ninja diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04c93efa..38f9b70f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,8 +120,6 @@ jobs: export CXX="${{matrix.cxx-override}}" fi - source scripts/prepare_breakpad_linux.sh - export PKG_CONFIG_PATH="$CUSTOM_BREAKPAD_PREFIX/lib/pkgconfig:${PKG_CONFIG_PATH:-}" # mkdir build cd build cmake --version @@ -136,7 +134,6 @@ jobs: -DPYTHON_EXECUTABLE="$CUTTER_DEPS_PYTHON_PREFIX/bin/python3" \ -DCUTTER_ENABLE_PYTHON_BINDINGS=ON \ -DCUTTER_ENABLE_GRAPHVIZ=OFF \ - -DCUTTER_ENABLE_CRASH_REPORTS=ON \ -DCUTTER_USE_BUNDLED_RIZIN=ON \ -DCUTTER_APPIMAGE_BUILD=ON \ -DCUTTER_ENABLE_PACKAGING=ON \ @@ -191,7 +188,6 @@ jobs: source cutter-deps/env.sh set -euo pipefail export PATH=/usr/local/opt/llvm/bin:$PATH - source scripts/prepare_breakpad_macos.sh mkdir build cd build PACKAGE_NAME=Cutter-${PACKAGE_ID}-macOS-x86_64 @@ -202,7 +198,6 @@ jobs: -DPYTHON_EXECUTABLE="$CUTTER_DEPS_PYTHON_PREFIX/bin/python3" \ -DCUTTER_ENABLE_PYTHON=ON \ -DCUTTER_ENABLE_PYTHON_BINDINGS=ON \ - -DCUTTER_ENABLE_CRASH_REPORTS=ON \ -DCUTTER_USE_BUNDLED_RIZIN=ON \ -DCUTTER_ENABLE_PACKAGING=ON \ -DCUTTER_ENABLE_SIGDB=ON \ @@ -213,7 +208,6 @@ jobs: -DCUTTER_PACKAGE_RZ_LIBSWIFT=ON \ -DCUTTER_PACKAGE_RZ_LIBYARA=ON \ -DCPACK_PACKAGE_FILE_NAME="$PACKAGE_NAME" \ - -DCMAKE_FRAMEWORK_PATH="$BREAKPAD_FRAMEWORK_DIR" \ -DCPACK_BUNDLE_APPLE_CERT_APP="-" \ .. && \ make -j4; @@ -237,7 +231,6 @@ jobs: set CUTTER_DEPS=%CD%\cutter-deps set PATH=%CD%\cutter-deps\qt\bin;%PATH% call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 - call scripts\prepare_breakpad.bat cd mkdir build cd build @@ -255,7 +248,6 @@ jobs: -DCUTTER_PACKAGE_RZ_LIBYARA=ON ^ -DCUTTER_PACKAGE_JSDEC=ON ^ -DCUTTER_ENABLE_DEPENDENCY_DOWNLOADS=ON ^ - -DCUTTER_ENABLE_CRASH_REPORTS=ON ^ -DCMAKE_PREFIX_PATH="%CUTTER_DEPS%\pyside" ^ -DCPACK_PACKAGE_FILE_NAME=%PACKAGE_NAME% ^ -G Ninja ^ diff --git a/.woodpecker/macos-arm64.yml b/.woodpecker/macos-arm64.yml index 96ce1457..bbff7e68 100644 --- a/.woodpecker/macos-arm64.yml +++ b/.woodpecker/macos-arm64.yml @@ -12,7 +12,6 @@ pipeline: - export PACKAGE_ID=${CI_COMMIT_TAG=git-`date "+%Y-%m-%d"`-${CI_COMMIT_SHA}} - export PACKAGE_NAME=Cutter-$${PACKAGE_ID}-macOS-arm64 - source cutter-deps/env.sh - - source scripts/prepare_breakpad_macos.sh - cmake -Bbuild -GNinja -DCMAKE_BUILD_TYPE=Release -DPYTHON_LIBRARY="$$CUTTER_DEPS_PYTHON_PREFIX/lib/libpython3.9.dylib" @@ -20,7 +19,6 @@ pipeline: -DPYTHON_EXECUTABLE="$$CUTTER_DEPS_PYTHON_PREFIX/bin/python3" -DCUTTER_ENABLE_PYTHON=ON -DCUTTER_ENABLE_PYTHON_BINDINGS=ON - -DCUTTER_ENABLE_CRASH_REPORTS=ON -DCUTTER_USE_BUNDLED_RIZIN=ON -DCUTTER_ENABLE_PACKAGING=ON -DCUTTER_ENABLE_SIGDB=ON @@ -31,7 +29,6 @@ pipeline: -DCUTTER_PACKAGE_RZ_LIBSWIFT=ON -DCUTTER_PACKAGE_RZ_LIBYARA=ON -DCPACK_PACKAGE_FILE_NAME="$$PACKAGE_NAME" - -DCMAKE_FRAMEWORK_PATH="$$BREAKPAD_FRAMEWORK_DIR" -DCPACK_BUNDLE_APPLE_CERT_APP="-" - ninja -C build package: diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c615e56..a62b602a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,6 @@ option(CUTTER_USE_ADDITIONAL_RIZIN_PATHS "Search rizin in additional paths which Disable this option if you are linking against rizin pacakged as proper system library or in a custom path and additional are paths causing problems." ON) option(CUTTER_ENABLE_PYTHON "Enable Python integration. Requires Python >= ${CUTTER_PYTHON_MIN}." OFF) option(CUTTER_ENABLE_PYTHON_BINDINGS "Enable generating Python bindings with Shiboken2. Unused if CUTTER_ENABLE_PYTHON=OFF." OFF) -option(CUTTER_ENABLE_CRASH_REPORTS "Enable crash report system. Unused if CUTTER_ENABLE_CRASH_REPORTS=OFF" OFF) option(CUTTER_APPIMAGE_BUILD "Enable Appimage specific changes. Doesn't cause building of Appimage itself." OFF) tri_option(CUTTER_ENABLE_KSYNTAXHIGHLIGHTING "Use KSyntaxHighlighting" AUTO) tri_option(CUTTER_ENABLE_GRAPHVIZ "Enable use of graphviz for graph layout" AUTO) @@ -136,7 +135,6 @@ if(CUTTER_USE_BUNDLED_RIZIN) endif() message(STATUS "- Python: ${CUTTER_ENABLE_PYTHON}") message(STATUS "- Python Bindings: ${CUTTER_ENABLE_PYTHON_BINDINGS}") -message(STATUS "- Crash Handling: ${CUTTER_ENABLE_CRASH_REPORTS}") message(STATUS "- KSyntaxHighlighting: ${KSYNTAXHIGHLIGHTING_STATUS}") message(STATUS "- Graphviz: ${CUTTER_ENABLE_GRAPHVIZ}") message(STATUS "- Downloads dependencies: ${CUTTER_ENABLE_DEPENDENCY_DOWNLOADS}") diff --git a/cmake/FindBreakpad.cmake b/cmake/FindBreakpad.cmake deleted file mode 100644 index 3cc01ee8..00000000 --- a/cmake/FindBreakpad.cmake +++ /dev/null @@ -1,65 +0,0 @@ -# - Find Breakpad -# -# Breakpad_FOUND - True if Breakpad has been found. -# Breakpad_INCLUDE_DIRS - Breakpad include directory -# Breakpad_LIBRARIES - List of libraries when using Breakpad. - -set(Breakpad_LIBRARIES_VARS "") -if(WIN32) - find_path(Breakpad_INCLUDE_DIRS - client/windows/handler/exception_handler.h - HINTS - "${CMAKE_CURRENT_SOURCE_DIR}/Breakpad/src/src") - - set(Breakpad_LIBRARY_NAMES - exception_handler - crash_generation_client - common - ) - - set(Breakpad_LIBRARIES "") - - foreach(libname ${Breakpad_LIBRARY_NAMES}) - find_library(Breakpad_LIBRARY_${libname} - ${libname} - HINTS - "${CMAKE_CURRENT_SOURCE_DIR}/Breakpad/src/src/client/windows/Release/lib" - REQUIRED) - - list(APPEND Breakpad_LIBRARIES ${Breakpad_LIBRARY_${libname}}) - list(APPEND Breakpad_LIBRARIES_VARS "Breakpad_LIBRARY_${libname}") - endforeach() - - set (Breakpad_LINK_LIBRARIES ${Breakpad_LIBRARIES}) - - set(Breakpad_LIBRARY_DIRS "") -elseif(APPLE) - find_library(Breakpad_LINK_LIBRARIES Breakpad REQUIRED) - set(Breakpad_LIBRARIES ${Breakpad_LINK_LIBRARIES}) - # Assumes Breakpad is packed as Framework - set(Breakpad_INCLUDE_DIRS "${Breakpad_LINK_LIBRARIES}/Headers") -else() - set(Breakpad_CMAKE_PREFIX_PATH_TEMP ${CMAKE_PREFIX_PATH}) - list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_SOURCE_DIR}/Breakpad/prefix") - - find_package(PkgConfig REQUIRED) - pkg_search_module(Breakpad REQUIRED breakpad-client) - - # reset CMAKE_PREFIX_PATH - set(CMAKE_PREFIX_PATH ${Breakpad_CMAKE_PREFIX_PATH_TEMP}) - mark_as_advanced(Breakpad_CMAKE_PREFIX_PATH_TEMP) -endif() - -# could be simplified in > cmake 3.11 using pkg_search_module IMPORTED_TARGET [GLOBAL] but this would still be required for windows -add_library(Breakpad::client INTERFACE IMPORTED) -set_target_properties(Breakpad::client PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${Breakpad_INCLUDE_DIRS}") -set_target_properties(Breakpad::client PROPERTIES - INTERFACE_LINK_LIBRARIES "${Breakpad_LINK_LIBRARIES}") - - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Breakpad REQUIRED_VARS Breakpad_LIBRARIES Breakpad_INCLUDE_DIRS ${Breakpad_LIBRARIES_VARS}) - -mark_as_advanced(Breakpad_LIBRARIES_VARS) - diff --git a/dist/CMakeLists.txt b/dist/CMakeLists.txt index ee962d2e..956fbd18 100644 --- a/dist/CMakeLists.txt +++ b/dist/CMakeLists.txt @@ -77,10 +77,6 @@ if(APPLE) set(CPACK_DMG_VOLUME_NAME "Cutter") set(CPACK_BUNDLE_APPLE_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/Entitlements.plist") set(CPACK_APPLE_BUNDLE_ID "re.rizin.cutter") - if (CUTTER_ENABLE_CRASH_REPORTS) - list(APPEND CPACK_BUNDLE_APPLE_CODESIGN_FILES "/Contents/Frameworks/Breakpad.framework/Versions/Current/Resources/breakpadUtilities.dylib") - endif() - find_program(MACDEPLOYQT_PATH macdeployqt HINTS "${Qt5_DIR}/../../../bin") if(NOT MACDEPLOYQT_PATH) diff --git a/dist/MacOSSetupBundle.cmake.in b/dist/MacOSSetupBundle.cmake.in index ff8bdb35..0259ac41 100644 --- a/dist/MacOSSetupBundle.cmake.in +++ b/dist/MacOSSetupBundle.cmake.in @@ -3,8 +3,6 @@ include(BundleUtilities) set(MACDEPLOYQT_PATH "@MACDEPLOYQT_PATH@") set(INFO_PLIST_PATH "@CPACK_BUNDLE_PLIST@") set(ADJUST_RIZIN_LIBS "@ADJUST_RIZIN_LIBS@") -set(CUTTER_ENABLE_CRASH_REPORTS "@CUTTER_ENABLE_CRASH_REPORTS@") -set(Breakpad_LINK_LIBRARIES "@Breakpad_LINK_LIBRARIES@") set(CUTTER_PACKAGE_DEPENDENCIES "@CUTTER_PACKAGE_DEPENDENCIES@") set(CUTTER_ENABLE_PYTHON "@CUTTER_ENABLE_PYTHON@") @@ -82,9 +80,3 @@ foreach(_lib ${ADJUST_RIZIN_LIBS}) get_filename_component(_name "${_lib}" NAME) file(REMOVE "${BUNDLE_PATH}/Contents/Frameworks/${_name}") endforeach() - -if (CUTTER_ENABLE_CRASH_REPORTS) - message("Copying Breakpad ${Breakpad_LINK_LIBRARIES}") - set(_breakpad_lib "Versions/A/Breakpad") - copy_resolved_framework_into_bundle("${Breakpad_LINK_LIBRARIES}/${_breakpad_lib}" "${FRAMEWORK_DIR}/Breakpad.framework/${_breakpad_lib}") -endif() diff --git a/docs/source/building.rst b/docs/source/building.rst index 5f2d9c0a..95e9cf74 100644 --- a/docs/source/building.rst +++ b/docs/source/building.rst @@ -113,8 +113,7 @@ If you want to use Cutter with another version of Rizin you can set ``-DCUTTER_U .. note:: If you are interested in building Cutter with support for Python plugins, - Syntax Highlighting, Crash Reporting and more, - please look at the full list of `CMake Building Options`_. + Syntax Highlighting and more, please look at the full list of `CMake Building Options`_. After the build process is complete, you should have the ``Cutter`` executable in the **build** dir. @@ -252,7 +251,6 @@ Note that there are some major building options available: Cutter binary release options, not needed for most users and might not work easily outside CI environment: -* ``CUTTER_ENABLE_CRASH_REPORTS`` is used to compile Cutter with crash handling system enabled (Breakpad). * ``CUTTER_ENABLE_DEPENDENCY_DOWNLOADS`` Enable downloading of dependencies. Setting to OFF doesn't affect any downloads done by Rizin build. This option is used for preparing Cutter binary release packges. Turned off by default. * ``CUTTER_PACKAGE_DEPENDENCIES`` During install step include the third party dependencies. This option is used for preparing Cutter binary release packges. @@ -271,28 +269,6 @@ Or if one wants to explicitly disable an option: cmake -B build -DCUTTER_ENABLE_PYTHON=OFF --------------- - -Compiling Cutter with Breakpad Support --------------------------------------- - -If you want to build Cutter with crash handling system, you will want to first prepare Breakpad. -For this, simply run one of the scripts (according to your OS) from root Cutter directory: - -.. code:: sh - - source scripts/prepare_breakpad_linux.sh # Linux - source scripts/prepare_breakpad_macos.sh # MacOS - scripts/prepare_breakpad.bat # Windows - -Then if you are building on Linux you want to change ``PKG_CONFIG_PATH`` environment variable -so it contains ``$CUSTOM_BREAKPAD_PREFIX/lib/pkgconfig``. For this simply run - -.. code:: sh - - export PKG_CONFIG_PATH="$CUSTOM_BREAKPAD_PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH" - - -------------- Troubleshooting diff --git a/docs/source/contributing/code/crash-handling-system.rst b/docs/source/contributing/code/crash-handling-system.rst deleted file mode 100644 index 98004b14..00000000 --- a/docs/source/contributing/code/crash-handling-system.rst +++ /dev/null @@ -1,32 +0,0 @@ -Crash Handling System -===================== - -Cutter uses `Breakpad `__ as a backend -for crash handling. - -Crash Handling System is disabled by default to not interfere with developers while debugging. -To enable this system, set the ``CUTTER_ENABLE_CRASH_REPORTS`` build option. - -Solution Description --------------------- - -There are only 2 source files: - -* ``CrashHandler.h`` -* ``CrashHandler.cpp`` - -And the API is very simple: One function, ``initCrashHandler()``, enables the Crash Handling System if -``CUTTER_ENABLE_CRASH_REPORTS`` is true, otherwise it does nothing. - -As soon as a signal is raised, ``crashHandler(int signum)`` is called with the signal's code as an argument. -This function first writes a crash dump to the operating system's temporary directory to catch core and -memory state as it was at the moment of the crash. - -Then the crash dialog is shown: - -.. image :: /images/crash-dialog.png - -If the user chooses to create a crash dump, the prepared dump is moved to the directory specified by the user. -And then the success dialog is shown: - -.. image :: /images/success-dump-dialog.png diff --git a/docs/source/images/crash-dialog.png b/docs/source/images/crash-dialog.png deleted file mode 100644 index a3e50eb26a75f75b0d771b6a2a47487c90107950..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29528 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

m53y>Rp=rgCTRm$}3(R5^&Rw^_e>IIHQ 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 DaC6T? diff --git a/docs/source/images/success-dump-dialog.png b/docs/source/images/success-dump-dialog.png deleted file mode 100644 index 94144c85718a97814131f5b28a4b1ba996daabd5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14629 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 -Date: Thu, 15 Nov 2018 13:56:01 -0500 -Subject: [PATCH] Update Breakpad.xib minimum target to OSX10.11 - -Bug: https://bugs.chromium.org/p/google-breakpad/issues/detail?id=778 -Change-Id: I32a36e47d0aab92e5ac40e7fbaa3c5caaaff2318 ---- - -diff --git a/src/client/mac/sender/Breakpad.xib b/src/client/mac/sender/Breakpad.xib -index 7966f89..1ecd27e 100644 ---- a/src/client/mac/sender/Breakpad.xib -+++ b/src/client/mac/sender/Breakpad.xib -@@ -1,1140 +1,224 @@ - -- -- -- 1050 -- 10F569 -- 762 -- 1038.29 -- 461.00 -- -- YES -- -- YES -- -- -- YES -- -- -- -- YES -- -- -- -- YES -- -- -- YES -- -- -- -- YES -- -- Reporter -- -- -- FirstResponder -- -- -- NSApplication -- -- -- 1 -- 2 -- {{72, 251}, {490, 489}} -- 536871936 -- -- NSWindow -- -- {1.79769e+308, 1.79769e+308} -- {72, 5} -- -- -- 264 -- -- YES -- -- -- 272 -- -- YES -- -- -- 256 -- -- YES -- -- -- 290 -- {{17, 36}, {456, 70}} -- -- YES -- -- 67239424 -- 272760832 -- Providing your email address is optional and will allow us contact you in case we need more details. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed arcu urna, pulvinar sit amet, tincidunt ac, fermentum ut, ligula. Quisque mi. Duis lectus. Vestibulum velit. Morbi turpis. Nunc at diam consectetur turpis volutpat tristique. Donec quis diam. Suspendisse scelerisque. -- -- LucidaGrande -- 11 -- 3100 -- -- -- -- 6 -- System -- controlColor -- -- 3 -- MC42NjY2NjY2NjY3AA -- -- -- -- 6 -- System -- controlTextColor -- -- 3 -- MAA -- -- -- -- -- -- -- 290 -- {{87, 9}, {195, 19}} -- -- YES -- -- -1804468671 -- 272761856 -- -- -- optional -- -- YES -- -- 6 -- System -- textBackgroundColor -- -- 3 -- MQA -- -- -- -- 6 -- System -- textColor -- -- -- -- -- -- -- 292 -- {{17, 11}, {65, 14}} -- -- YES -- -- 68288064 -- 71435264 -- EmailLabel: -- -- -- -- -- -- -- -- -- 289 -- {{456, 10}, {16, 17}} -- -- YES -- -- -2080244224 -- 0 -- Privacy Policy -- -- LucidaGrande -- 13 -- 1044 -- -- -- -2040250113 -- 36 -- -- NSImage -- goArrow -- -- -- -- 400 -- 75 -- -- -- -- -- 289 -- {{355, 11}, {100, 14}} -- -- YES -- -- 68288064 -- 4326400 -- PrivacyPolicyLabel -- -- -- -- -- -- -- -- {490, 114} -- -- -- -- {{0, 51}, {490, 114}} -- -- {0, 0} -- -- 67239424 -- 0 -- Title -- -- LucidaGrande -- 11 -- 16 -- -- -- -- 3 -- MCAwLjgwMDAwMDAxAA -- -- -- -- 0 -- 3 -- 0 -- NO -- -- -- -- 289 -- {{330, 12}, {146, 32}} -- -- YES -- -- 67239424 -- 134217728 -- SendReportLabel -- -- -- -2038284033 -- 129 -- -- -- DQ -- 200 -- 25 -- -- -- -- -- 289 -- {{214, 12}, {116, 32}} -- -- YES -- -- 67239424 -- 134217728 -- CancelLabel -- -- -- -2038284033 -- 129 -- -- -- Gw -- 200 -- 25 -- -- -- -- -- 256 -- -- YES -- -- -- 256 -- -- YES -- -- -- 266 -- {{17, 83}, {456, 154}} -- -- YES -- -- 67239424 -- 272760832 -- VGhlIHN5c3RlbSBhbmQgb3RoZXIgYXBwbGljYXRpb25zIGhhdmUgbm90IGJlZW4gYWZmZWN0ZWQuIEEg --cmVwb3J0IGhhcyBiZWVuIGNyZWF0ZWQgdGhhdCB5b3UgY2FuIHNlbmQgdG8gPFJlYWxseSBMb25nIENv --bXBhbnkgTmFtZT4gdG8gaGVscCBpZGVudGlmeSB0aGUgcHJvYmxlbS4gTG9yZW0gaXBzdW0gZG9sb3Ig --c2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gU2VkIGFyY3UgdXJuYSwgcHVsdmlu --YXIgc2l0IGFtZXQsIHRpbmNpZHVudCBhYywgZmVybWVudHVtIHV0LCBsaWd1bGEuIFF1aXNxdWUgbWku --IER1aXMgbGVjdHVzLiBWZXN0aWJ1bHVtIHZlbGl0LiBNb3JiaSB0dXJwaXMuIE51bmMgYXQgZGlhbSBj --b25zZWN0ZXR1ciB0dXJwaXMgdm9sdXRwYXQgdHJpc3RpcXVlLiBEb25lYyBxdWlzIGRpYW0uIFN1c3Bl --bmRpc3NlIHNjZWxlcmlzcXVlLiBRdWlzcXVlIHB1bHZpbmFyIG1pIGlkIHB1cnVzLiBFdGlhbSB2aXRh --ZSB0dXJwaXMgdml0YWUgbmVxdWUgcG9ydGEgY29uZ3VlLgoKUGxlYXNlIGhlbHAgdXMgZml4IHRoZSBw --cm9ibGVtIGJ5IGRlc2NyaWJpbmcgd2hhdCBoYXBwZW5lZCBiZWZvcmUgdGhlIGNyYXNoLiBMb3JlbSBp --cHN1bSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIgYWRpcGlzY2luZyBlbGl0LiBTZWQgYXJjdSB1 --cm5hLCBwdWx2aW5hciBzaXQgYW1ldCwgdGluY2lkdW50IGFjLCBmZXJtZW50dW0gdXQsIGxpZ3VsYS4g --UXVpc3F1ZSBtaS4gRHVpcyBsZWN0dXMuA -- -- -- -- -- -- -- -- -- 274 -- {{20, 14}, {450, 61}} -- -- YES -- -- 341966337 -- 272760832 -- Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 -- -- -- YES -- -- -- -- -- -- -- 256 -- -- YES -- -- -- 256 -- -- YES -- -- -- 266 -- {{85, 10}, {381, 54}} -- -- YES -- -- 67239424 -- 272629760 -- The application <Really Long App Name Here> has quit unexpectedly. -- -- LucidaGrande-Bold -- 14 -- 16 -- -- -- -- -- -- -- -- -- 268 -- -- YES -- -- YES -- Apple PDF pasteboard type -- Apple PICT pasteboard type -- Apple PNG pasteboard type -- NSFilenamesPboardType -- NeXT Encapsulated PostScript v1.2 pasteboard type -- NeXT TIFF v4.0 pasteboard type -- -- -- {{16, 0}, {64, 64}} -- -- YES -- -- 130560 -- 33554432 -- -- NSImage -- NSApplicationIcon -- -- 0 -- 0 -- 0 -- NO -- -- YES -- -- -- {482, 70} -- -- -- -- {{4, 245}, {482, 70}} -- -- {0, 0} -- -- 67239424 -- 0 -- Title -- -- -- -- 3 -- MCAwLjgwMDAwMDAxAA -- -- -- -- 0 -- 3 -- 0 -- NO -- -- -- {490, 325} -- -- -- -- {{0, 160}, {490, 325}} -- -- {0, 0} -- -- 67239424 -- 0 -- Title -- -- -- -- 3 -- MCAwLjgwMDAwMDAxAA -- -- -- -- 0 -- 3 -- 0 -- NO -- -- -- -- 268 -- {{17, 20}, {163, 14}} -- -- YES -- -- 68288064 -- 272630784 -- xx seconds. -- -- -- -- -- -- -- -- {490, 489} -- -- {{0, 0}, {2560, 1578}} -- {72, 27} -- {1.79769e+308, 1.79769e+308} -- -- -- YES -- -- -- -- -- YES -- -- -- sendReport: -- -- -- -- 45 -- -- -- -- cancel: -- -- -- -- 46 -- -- -- -- showPrivacyPolicy: -- -- -- -- 53 -- -- -- -- value: emailValue -- -- -- -- -- -- value: emailValue -- value -- emailValue -- -- NSNullPlaceholder -- optional -- -- 2 -- -- -- 90 -- -- -- -- initialFirstResponder -- -- -- -- 91 -- -- -- -- value: commentsValue -- -- -- -- -- -- value: commentsValue -- value -- commentsValue -- -- NSNullPlaceholder -- optional comments -- -- 2 -- -- -- 124 -- -- -- -- nextKeyView -- -- -- -- 125 -- -- -- -- nextKeyView -- -- -- -- 126 -- -- -- -- nextKeyView -- -- -- -- 127 -- -- -- -- delegate -- -- -- -- 128 -- -- -- -- alertWindow_ -- -- -- -- 142 -- -- -- -- preEmailBox_ -- -- -- -- 150 -- -- -- -- headerBox_ -- -- -- -- 151 -- -- -- -- emailSectionBox_ -- -- -- -- 152 -- -- -- -- privacyLinkLabel_ -- -- -- -- 153 -- -- -- -- commentMessage_ -- -- -- -- 154 -- -- -- -- dialogTitle_ -- -- -- -- 155 -- -- -- -- emailLabel_ -- -- -- -- 156 -- -- -- -- cancelButton_ -- -- -- -- 158 -- -- -- -- sendButton_ -- -- -- -- 159 -- -- -- -- emailEntryField_ -- -- -- -- 161 -- -- -- -- privacyLinkArrow_ -- -- -- -- 162 -- -- -- -- emailMessage_ -- -- -- -- 163 -- -- -- -- commentsEntryField_ -- -- -- -- 176 -- -- -- -- value: countdownMessage -- -- -- -- -- -- value: countdownMessage -- value -- countdownMessage -- 2 -- -- -- 194 -- -- -- -- countdownLabel_ -- -- -- -- 208 -- -- -- -- -- YES -- -- 0 -- -- -- -- -- -- -2 -- -- -- File's Owner -- -- -- -1 -- -- -- First Responder -- -- -- -3 -- -- -- Application -- -- -- 1 -- -- -- YES -- -- -- -- Window -- -- -- 2 -- -- -- YES -- -- -- -- -- -- -- -- -- -- 12 -- -- -- YES -- -- -- -- -- -- 14 -- -- -- YES -- -- -- -- -- -- 132 -- -- -- YES -- -- -- -- -- -- -- -- -- -- 145 -- -- -- YES -- -- -- -- -- -- -- -- 189 -- -- -- YES -- -- -- -- -- -- 191 -- -- -- Shared User Defaults Controller -- -- -- 210 -- -- -- -- -- 211 -- -- -- -- -- 221 -- -- -- -- -- 58 -- -- -- YES -- -- -- -- -- -- 215 -- -- -- -- -- 18 -- -- -- YES -- -- -- -- -- -- 212 -- -- -- -- -- 20 -- -- -- YES -- -- -- -- -- -- 213 -- -- -- -- -- 48 -- -- -- YES -- -- -- -- -- -- 214 -- -- -- -- -- 66 -- -- -- YES -- -- -- -- -- -- 216 -- -- -- -- -- 8 -- -- -- YES -- -- -- -- -- -- 217 -- -- -- -- -- 116 -- -- -- YES -- -- -- -- -- -- 218 -- -- -- -- -- 147 -- -- -- YES -- -- -- -- -- -- -- 3 -- -- -- YES -- -- -- -- -- -- 219 -- -- -- -- -- 6 -- -- -- YES -- -- -- -- -- -- 220 -- -- -- -- -- -- -- YES -- -- YES -- -3.ImportedFromIB2 -- 1.IBEditorWindowLastContentRect -- 1.IBWindowTemplateEditedContentRect -- 1.ImportedFromIB2 -- 1.windowTemplate.hasMinSize -- 1.windowTemplate.minSize -- 116.CustomClassName -- 116.ImportedFromIB2 -- 12.ImportedFromIB2 -- 132.ImportedFromIB2 -- 14.ImportedFromIB2 -- 145.ImportedFromIB2 -- 147.ImportedFromIB2 -- 18.CustomClassName -- 18.ImportedFromIB2 -- 189.ImportedFromIB2 -- 191.ImportedFromIB2 -- 2.ImportedFromIB2 -- 20.ImportedFromIB2 -- 3.ImportedFromIB2 -- 48.ImportedFromIB2 -- 58.ImportedFromIB2 -- 6.ImportedFromIB2 -- 66.ImportedFromIB2 -- 8.ImportedFromIB2 -- -- -- YES -- -- {{0, 656}, {490, 489}} -- {{0, 656}, {490, 489}} -- -- -- {72, 5} -- LengthLimitingTextField -- -- -- -- -- -- -- LengthLimitingTextField -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- YES -- -- -- YES -- -- -- -- -- YES -- -- -- YES -- -- -- -- 221 -- -- -- -- YES -- -- LengthLimitingTextField -- NSTextField -- -- IBUserSource -- -- -- -- -- Reporter -- NSObject -- -- YES -- -- YES -- cancel: -- sendReport: -- showPrivacyPolicy: -- -- -- YES -- id -- id -- id -- -- -- -- YES -- -- YES -- alertWindow_ -- cancelButton_ -- commentMessage_ -- commentsEntryField_ -- countdownLabel_ -- dialogTitle_ -- emailEntryField_ -- emailLabel_ -- emailMessage_ -- emailSectionBox_ -- headerBox_ -- preEmailBox_ -- privacyLinkArrow_ -- privacyLinkLabel_ -- sendButton_ -- -- -- YES -- NSWindow -- NSButton -- NSTextField -- LengthLimitingTextField -- NSTextField -- NSTextField -- LengthLimitingTextField -- NSTextField -- NSTextField -- NSBox -- NSBox -- NSBox -- NSView -- NSTextField -- NSButton -- -- -- -- IBUserSource -- -- -- -- -- -- 0 -- IBCocoaFramework -- -- com.apple.InterfaceBuilder.CocoaPlugin.macosx -- -- -- -- com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 -- -- -- YES -- ../Breakpad.xcodeproj -- 3 -- -- YES -- -- YES -- NSApplicationIcon -- goArrow -- -- -- YES -- {128, 128} -- {128, 128} -- -- -- -- -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Providing your email address is optional and will allow us contact you in case we need more details. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed arcu urna, pulvinar sit amet, tincidunt ac, fermentum ut, ligula. Quisque mi. Duis lectus. Vestibulum velit. Morbi turpis. Nunc at diam consectetur turpis volutpat tristique. Donec quis diam. Suspendisse scelerisque. -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ optional -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ The system and other applications have not been affected. A report has been created that you can send to <Really Long Company Name> to help identify the problem. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed arcu urna, pulvinar sit amet, tincidunt ac, fermentum ut, ligula. Quisque mi. Duis lectus. Vestibulum velit. Morbi turpis. Nunc at diam consectetur turpis volutpat tristique. Donec quis diam. Suspendisse scelerisque. Quisque pulvinar mi id purus. Etiam vitae turpis vitae neque porta congue. -+ -+Please help us fix the problem by describing what happened before the crash. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed arcu urna, pulvinar sit amet, tincidunt ac, fermentum ut, ligula. Quisque mi. Duis lectus. -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 -+ -+ -+ -+ -+ -+ -+ optional comments -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ diff --git a/scripts/prepare_breakpad.bat b/scripts/prepare_breakpad.bat deleted file mode 100644 index fa4fca45..00000000 --- a/scripts/prepare_breakpad.bat +++ /dev/null @@ -1,33 +0,0 @@ -@ECHO OFF -SET ROOT_DIR=%CD% - -powershell -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; wget https://storage.googleapis.com/chrome-infra/depot_tools.zip -OutFile depot_tools.zip " -7z -bd x %ROOT_DIR%\depot_tools.zip -odepot_tools -powershell -Command "depot_tools\update_depot_tools" -SET BUFF_PATH=%PATH% -SET DEPOT_TOOLS=%ROOT_DIR%\depot_tools -set PATH=%DEPOT_TOOLS%;%BUFF_PATH% - -mkdir %ROOT_DIR%\src\breakpad -CD %ROOT_DIR%\src\breakpad -powershell -Command "fetch breakpad" -powershell -Command "gclient sync" -CD %ROOT_DIR%\src\breakpad\src -powershell -Command "git reset --hard 756daa536ad819eff80172aaab262fb71d1e89fd" - -CD %ROOT_DIR%\src\breakpad\src\src\client\windows -DEL %CD%\breakpad_client.gyp -DEL %CD%\breakpad_client.sln -DEL %CD%\common.vcxproj -DEL %CD%\common.vcxproj.filters -DEL %CD%\build_all.vcxproj -COPY %ROOT_DIR%\scripts\breakpad_client.gyp %CD% - -CD %ROOT_DIR%\src\breakpad\src\src -SET GYP_MSVS_VERSION=2017 -powershell -Command "tools\gyp\gyp.bat --no-circular-check client\windows\breakpad_client.gyp -Dwin_release_RuntimeLibrary=2 -Dwin_debug_RuntimeLibrary=2 -Dplatform=%ARCH% -Dconfiguration=release" -devenv client\windows\breakpad_client.sln /upgrade - -set PATH=%BUFF_PATH% -msbuild /m %CD%\client\windows\breakpad_client.sln /p:Configuration=release /p:Platform=%ARCH% || exit /b 1 -CD %ROOT_DIR% diff --git a/scripts/prepare_breakpad_linux.sh b/scripts/prepare_breakpad_linux.sh deleted file mode 100755 index d0dd82b6..00000000 --- a/scripts/prepare_breakpad_linux.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -set -euo pipefail - -git clone https://github.com/google/breakpad.git -cd breakpad -git clone https://chromium.googlesource.com/linux-syscall-support src/third_party/lss -CFLAGS=-w CXXFLAGS=-w ./configure --disable-tools --prefix=`pwd`/prefix && make -j4 && make install || exit 1 - -export CUSTOM_BREAKPAD_PREFIX="`pwd`/prefix" -cd .. diff --git a/scripts/prepare_breakpad_macos.sh b/scripts/prepare_breakpad_macos.sh deleted file mode 100755 index 9c1e2683..00000000 --- a/scripts/prepare_breakpad_macos.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -set -euo pipefail - -SCRIPTPATH=$(realpath "$(dirname "${BASH_SOURCE[0]}")") - -DIR="$SCRIPTPATH/.." -cd "$DIR" -BREAKPAD_FRAMEWORK_DIR="$DIR/breakpad/framework" -BREAKPAD_DUMP_SYMS_DIR="$DIR/breakpad/bin" -git clone https://github.com/google/breakpad.git -mkdir $BREAKPAD_FRAMEWORK_DIR -mkdir $BREAKPAD_DUMP_SYMS_DIR -cd breakpad -git checkout 4d550cceca107f36c4bc1ea1126b7d32cc50f424 -git apply "$SCRIPTPATH/breakpad_macos.patch" -cd src/client/mac/ && xcodebuild -sdk macosx MACOSX_DEPLOYMENT_TARGET=10.14 -cp -R build/Release/Breakpad.framework "$BREAKPAD_FRAMEWORK_DIR" - -cd $DIR/breakpad -cp -R src/. framework/Breakpad.framework/Headers - -export BREAKPAD_FRAMEWORK_DIR=$BREAKPAD_FRAMEWORK_DIR -cd $DIR diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e3c2c97d..f340ee6e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -262,7 +262,6 @@ set(HEADER_FILES common/RunScriptTask.h common/Json.h dialogs/EditMethodDialog.h - common/CrashHandler.h dialogs/TypesInteractionDialog.h widgets/SdbWidget.h plugins/PluginManager.h @@ -393,10 +392,6 @@ if (CUTTER_ENABLE_PYTHON) list(APPEND HEADER_FILES common/QtResImporter.h common/PythonManager.h common/PythonAPI.h) endif() -if(CUTTER_ENABLE_CRASH_REPORTS) - list(APPEND SOURCES common/CrashHandler.cpp) -endif() - if(CUTTER_ENABLE_PYTHON_BINDINGS) set(BINDINGS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/bindings") set(BINDINGS_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/bindings") @@ -471,19 +466,6 @@ if (TARGET Graphviz::GVC) target_compile_definitions(Cutter PRIVATE CUTTER_ENABLE_GRAPHVIZ) endif() -if(CUTTER_ENABLE_CRASH_REPORTS) - set(THREADS_PREFER_PTHREAD_FLAG ON) - find_package(Threads REQUIRED) - target_link_libraries(Cutter PRIVATE Threads::Threads) - - add_definitions(-DCUTTER_ENABLE_CRASH_REPORTS) - if (NOT WIN32) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g ") - endif() - find_package(Breakpad REQUIRED) - target_link_libraries(Cutter PRIVATE Breakpad::client) -endif() - target_link_libraries(Cutter PUBLIC ${QT_PREFIX}::Core ${QT_PREFIX}::Widgets ${QT_PREFIX}::Gui PRIVATE ${QT_PREFIX}::Svg ${QT_PREFIX}::Network) if (CUTTER_QT6) target_link_libraries(Cutter PUBLIC Qt6::Core5Compat Qt6::SvgWidgets) diff --git a/src/CutterApplication.cpp b/src/CutterApplication.cpp index 9d4d4dff..6f0612b4 100644 --- a/src/CutterApplication.cpp +++ b/src/CutterApplication.cpp @@ -1,5 +1,4 @@ #include "common/PythonManager.h" -#include "common/CrashHandler.h" #include "CutterApplication.h" #include "plugins/PluginManager.h" #include "CutterConfig.h" diff --git a/src/Main.cpp b/src/Main.cpp index 9c45f5e3..8650b94e 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -3,7 +3,6 @@ #include "core/MainWindow.h" #include "common/UpdateWorker.h" #include "CutterConfig.h" -#include "common/CrashHandler.h" #include "common/SettingsUpgrade.h" #include @@ -55,17 +54,6 @@ static void connectToConsole() int main(int argc, char *argv[]) { -#ifdef CUTTER_ENABLE_CRASH_REPORTS - if (argc >= 3 && QString::fromLocal8Bit(argv[1]) == "--start-crash-handler") { - QApplication app(argc, argv); - QString dumpLocation = QString::fromLocal8Bit(argv[2]); - showCrashDialog(dumpLocation); - return 0; - } - - initCrashHandler(); -#endif - #ifdef Q_OS_WIN connectToConsole(); #endif diff --git a/src/common/CrashHandler.cpp b/src/common/CrashHandler.cpp deleted file mode 100644 index 4c6e56b8..00000000 --- a/src/common/CrashHandler.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include "CrashHandler.h" -#include "BugReporting.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#if defined(Q_OS_LINUX) -# include "client/linux/handler/exception_handler.h" -#elif defined(Q_OS_WIN32) -# include "client/windows/handler/exception_handler.h" -#elif defined(Q_OS_MACOS) -# include "client/mac/handler/exception_handler.h" -#endif // Q_OS - -static google_breakpad::ExceptionHandler *exceptionHandler = nullptr; - -static void finishCrashHandler() -{ - delete exceptionHandler; -} - -#ifdef Q_OS_WIN32 -// Called if crash dump was successfully created -// Saves path to file -bool callback(const wchar_t *_dump_dir, const wchar_t *_minidump_id, void *context, - EXCEPTION_POINTERS *exinfo, MDRawAssertionInfo *assertion, bool success) -{ - const QDir dir = QString::fromWCharArray(_dump_dir); - const QString id = QString::fromWCharArray(_minidump_id); - QProcess::startDetached(QCoreApplication::applicationFilePath(), - { "--start-crash-handler", dir.filePath(id + ".dmp") }); - _exit(1); - return true; -} -#elif defined(Q_OS_LINUX) -// Called if crash dump was successfully created -// Saves path to file -bool callback(const google_breakpad::MinidumpDescriptor &md, void *context, bool b) -{ - QProcess::startDetached(QCoreApplication::applicationFilePath(), - { "--start-crash-handler", md.path() }); - _exit(1); - return true; -} -#elif defined(Q_OS_MACOS) -// Called if crash dump was successfully created -// Saves path to file -bool callback(const char *dump_dir, const char *minidump_id, void *context, bool succeeded) -{ - const QDir dir = QString::fromUtf8(dump_dir); - const QString id = QString::fromUtf8(minidump_id); - QProcess::startDetached(QCoreApplication::applicationFilePath(), - { "--start-crash-handler", dir.filePath(id + ".dmp") }); - _exit(1); - return true; -} -#endif // Q_OS - -void initCrashHandler() -{ - if (exceptionHandler) { - return; - } - // Here will be placed crash dump at the first place - // and then moved if needed - -#if defined(Q_OS_LINUX) - static std::string tmpLocation = - QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString(); - exceptionHandler = new google_breakpad::ExceptionHandler( - google_breakpad::MinidumpDescriptor(tmpLocation), nullptr, callback, nullptr, true, -1); -#elif defined(Q_OS_MACOS) - static std::string tmpLocation = - QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString(); - exceptionHandler = new google_breakpad::ExceptionHandler(tmpLocation, nullptr, callback, - nullptr, true, nullptr); -#else - static std::wstring tmpLocation = - QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdWString(); - exceptionHandler = - new google_breakpad::ExceptionHandler(tmpLocation, nullptr, callback, nullptr, - google_breakpad::ExceptionHandler::HANDLER_ALL); -#endif - atexit(finishCrashHandler); -} - -void showCrashDialog(const QString &dumpFile) -{ - QMessageBox mb; - mb.setWindowTitle(QObject::tr("Crash")); - mb.setText(QObject::tr("Cutter received a signal it can't handle and will close.
" - "Would you like to create a crash dump for a bug report?")); - mb.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - mb.button(QMessageBox::Yes)->setText(QObject::tr("Create a Crash Dump")); - mb.button(QMessageBox::No)->setText(QObject::tr("Quit")); - mb.setDefaultButton(QMessageBox::Yes); - - bool ok = false; - int ret = mb.exec(); - if (ret == QMessageBox::Yes) { - QString dumpSaveFileName; - int placementFailCounter = 0; - do { - placementFailCounter++; - if (placementFailCounter == 4) { - break; - } - dumpSaveFileName = QFileDialog::getSaveFileName( - nullptr, QObject::tr("Choose a directory to save the crash dump in"), - QStandardPaths::writableLocation(QStandardPaths::HomeLocation) - + QDir::separator() + "Cutter_crash_dump_" - + QDate::currentDate().toString("dd.MM.yy") + "_" - + QTime::currentTime().toString("HH.mm.ss") + ".dmp", - QObject::tr("Minidump (*.dmp)")); - - if (dumpSaveFileName.isEmpty()) { - return; - } - if (QFile::rename(dumpFile, dumpSaveFileName)) { - ok = true; - break; - } - QMessageBox::critical(nullptr, QObject::tr("Save Crash Dump"), - QObject::tr("Failed to write to %1.
" - "Please make sure you have access to that directory " - "and try again.") - .arg(QFileInfo(dumpSaveFileName).dir().path())); - } while (true); - - if (ok) { - QMessageBox info; - info.setWindowTitle(QObject::tr("Success")); - info.setText(QObject::tr("Crash dump was successfully created.") - .arg(QFileInfo(dumpSaveFileName).dir().path())); - info.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - - info.button(QMessageBox::Yes)->setText(QObject::tr("Open an Issue")); - info.button(QMessageBox::No)->setText(QObject::tr("Quit")); - info.setDefaultButton(QMessageBox::Yes); - - int ret = info.exec(); - if (ret == QMessageBox::Yes) { - openIssue(); - } - } else { - QMessageBox::critical(nullptr, QObject::tr("Error"), - QObject::tr("Error occurred during crash dump creation.")); - } - } else { - QFile f(dumpFile); - f.remove(); - } -} diff --git a/src/common/CrashHandler.h b/src/common/CrashHandler.h deleted file mode 100644 index 77ef6bf2..00000000 --- a/src/common/CrashHandler.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef CRASH_HANDLER_H -#define CRASH_HANDLER_H - -#include - -/** - * @fn void initCrashHandler() - * - * If CUTTER_ENABLE_CRASH_REPORTS is true, initializes - * crash handling and reporting, otherwise does nothing. - */ -void initCrashHandler(); - -void showCrashDialog(const QString &dumpFile); - -#endif // CRASH_HANDLER_H From 3921ba172e2f97e50f3e9e1d4795d91848bb5d28 Mon Sep 17 00:00:00 2001 From: Lovecraft's_Cat <52252627+Lovecrafts-Cat@users.noreply.github.com> Date: Mon, 16 Jan 2023 10:33:08 +0100 Subject: [PATCH 55/77] Added Scoop in the Windows download instructions (#3071) Since Scoop supports this project, it would be sensible to make it explicit for Windows users that it's an available install option. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5642875b..bdd37783 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,11 @@ Cutter release binaries for all major platforms (Linux, macOS, Windows) can be d - **macOS**: Download the `.dmg` file or use [Homebrew Cask](https://github.com/Homebrew/homebrew-cask): `brew install --cask cutter` -- **Windows**: Download the `.zip` archive or use [Chocolatey](https://chocolatey.org): +- **Windows**: Download the `.zip` archive, or use either [Chocolatey](https://chocolatey.org) or [Scoop](https://scoop.sh/): `choco install cutter` + + `scoop bucket add extras` followed by `scoop install cutter` ### Build from sources From 5144c3f3b50677e9eb1c1a91ea78fbfef67684b9 Mon Sep 17 00:00:00 2001 From: Rohit Bisht <74498290+R-ohit-B-isht@users.noreply.github.com> Date: Thu, 19 Jan 2023 07:19:54 +0530 Subject: [PATCH 56/77] Support graph and disassembly scrolling via PgUp and PgDown --- src/widgets/GraphView.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/widgets/GraphView.cpp b/src/widgets/GraphView.cpp index 95b81348..54b4b438 100644 --- a/src/widgets/GraphView.cpp +++ b/src/widgets/GraphView.cpp @@ -703,7 +703,10 @@ void GraphView::mouseDoubleClickEvent(QMouseEvent *event) void GraphView::keyPressEvent(QKeyEvent *event) { + // for scrolling with arrow keys const int delta = static_cast(30.0 / current_scale); + // for scrolling with pgup/pgdown keys + const int delta2 = static_cast(100.0 / current_scale); int dx = 0, dy = 0; switch (event->key()) { case Qt::Key_Up: @@ -718,6 +721,12 @@ void GraphView::keyPressEvent(QKeyEvent *event) case Qt::Key_Right: dx = delta; break; + case Qt::Key_PageUp: + dy = -delta2; + break; + case Qt::Key_PageDown: + dy = delta2; + break; default: QAbstractScrollArea::keyPressEvent(event); return; From 7f0daf96ddcaf544fa8c2935afc04c2cc172abbf Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Fri, 27 Jan 2023 17:46:12 +0800 Subject: [PATCH 57/77] Update Rizin to the latest `dev` (#3086) --- rizin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rizin b/rizin index d9950f74..b740cba3 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit d9950f74792c1dfb565ac491cc7ef706b80e6044 +Subproject commit b740cba35d43327109ed238fa5562edf01d619c2 From baeffba7d1b2f783c06cc3af51ba7533b3f6df15 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Sun, 29 Jan 2023 16:51:48 +0800 Subject: [PATCH 58/77] Fix #3087 - use new RzStrEnc for RzBinString (#3088) --- src/core/Cutter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index b21a1e03..dc585dd4 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -3330,7 +3330,7 @@ QList CutterCore::getAllStrings() StringDescription string; string.string = rz_str_escape_utf8_keep_printable(str->string, &opt); string.vaddr = obj ? rva(obj, str->paddr, str->vaddr, va) : str->paddr; - string.type = str->type; + string.type = rz_str_enc_as_string(str->type); string.size = str->size; string.length = str->length; string.section = section ? section->name : ""; From 9b093d6f4c76fc52035e93eec49bcc8f43ccfc91 Mon Sep 17 00:00:00 2001 From: Khairul Azhar Kasmiran Date: Sun, 5 Feb 2023 22:06:59 +0800 Subject: [PATCH 59/77] Add note on missing `qt5-default` in latest Debian dists (#3106) --- docs/source/building.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/building.rst b/docs/source/building.rst index 95e9cf74..924e638e 100644 --- a/docs/source/building.rst +++ b/docs/source/building.rst @@ -63,6 +63,9 @@ On Debian-based Linux distributions, all of these essential packages can be inst sudo apt install build-essential cmake meson libzip-dev zlib1g-dev qt5-default libqt5svg5-dev qttools5-dev qttools5-dev-tools +.. note:: + On Debian 11 (bullseye) and higher or Ubuntu 22.04 (Jammy) and higher, replace ``qt5-default`` above with ``qtbase5-dev``. + Depending on your configuration you'll might also need the following: :: From 9bf4dd3be2767b72cbe2f1bc9f58d6323be21fa2 Mon Sep 17 00:00:00 2001 From: Khairul Azhar Kasmiran Date: Wed, 8 Feb 2023 00:48:33 +0800 Subject: [PATCH 60/77] Run always the bundled Rizin's Ninja (#3108) This ensures that the bundled Rizin will always be built when Cutter is, to compile with any changes made to Rizin. --- cmake/BundledRizin.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/BundledRizin.cmake b/cmake/BundledRizin.cmake index c6982838..85ddd5b5 100644 --- a/cmake/BundledRizin.cmake +++ b/cmake/BundledRizin.cmake @@ -40,6 +40,7 @@ ExternalProject_Add(Rizin-Bundled SOURCE_DIR "${RIZIN_SOURCE_DIR}" CONFIGURE_COMMAND "${MESON}" "" ${MESON_OPTIONS} && "${MESON}" configure ${MESON_OPTIONS} --buildtype "$<$:debug>$<$>:release>" BUILD_COMMAND "${NINJA}" + BUILD_ALWAYS TRUE INSTALL_COMMAND "${NINJA}" install) set(Rizin_INCLUDE_DIRS "${RIZIN_INSTALL_DIR}/include/librz" "${RIZIN_INSTALL_DIR}/include/librz/sdb") From 3e0bc74e77091ee15af5b077ce37a4f33b3685f4 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Wed, 8 Feb 2023 01:04:14 +0800 Subject: [PATCH 61/77] Use git branch and revision in version string (#3109) --- CMakeLists.txt | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a62b602a..ce66a7a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,9 +38,27 @@ set(CUTTER_VERSION_MAJOR 2) set(CUTTER_VERSION_MINOR 1) set(CUTTER_VERSION_PATCH 2) -set(CUTTER_VERSION_FULL "${CUTTER_VERSION_MAJOR}.${CUTTER_VERSION_MINOR}.${CUTTER_VERSION_PATCH}") +set(CUTTER_VERSION "${CUTTER_VERSION_MAJOR}.${CUTTER_VERSION_MINOR}.${CUTTER_VERSION_PATCH}") -project(Cutter VERSION "${CUTTER_VERSION_FULL}") +execute_process(COMMAND git log --pretty=format:'%h' -n 1 + OUTPUT_VARIABLE GIT_REV + ERROR_QUIET) + +# Check whether we got any revision (which isn't +# always the case, e.g. when someone downloaded a zip file +if ("${GIT_REV}" STREQUAL "") + set(CUTTER_VERSION_FULL "${CUTTER_VERSION}") +else() + execute_process( + COMMAND git rev-parse --abbrev-ref HEAD + OUTPUT_VARIABLE GIT_BRANCH) + string(STRIP "${GIT_REV}" GIT_REV) + string(SUBSTRING "${GIT_REV}" 1 7 GIT_REV) + string(STRIP "${GIT_BRANCH}" GIT_BRANCH) + set(CUTTER_VERSION_FULL "${CUTTER_VERSION}-${GIT_BRANCH}-${GIT_REV}") +endif() + +project(Cutter VERSION "${CUTTER_VERSION}") # Enable solution folder support set_property(GLOBAL PROPERTY USE_FOLDERS ON) From bbd4961468f0ca408e5442473f56c75b0fd84781 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Tue, 7 Feb 2023 21:28:40 +0800 Subject: [PATCH 62/77] Use QFontMetrics instead of fontMetrics() --- src/core/MainWindow.cpp | 2 +- src/widgets/ColorThemeListView.cpp | 15 +++++++++------ src/widgets/DisassemblyWidget.cpp | 2 +- src/widgets/SimpleTextGraphView.cpp | 3 ++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 46c6fac0..e873b0d7 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -1114,7 +1114,7 @@ void MainWindow::initBackForwardMenu() connect(button, &QWidget::customContextMenuRequested, button, [menu, button](const QPoint &pos) { menu->exec(button->mapToGlobal(pos)); }); - QFontMetrics metrics(fontMetrics()); + QFontMetrics metrics(font()); // Roughly 10-16 lines depending on padding size, no need to calculate more precisely menu->setMaximumHeight(metrics.lineSpacing() * 20); diff --git a/src/widgets/ColorThemeListView.cpp b/src/widgets/ColorThemeListView.cpp index 7d534f57..4f56ba17 100644 --- a/src/widgets/ColorThemeListView.cpp +++ b/src/widgets/ColorThemeListView.cpp @@ -48,8 +48,9 @@ void ColorOptionDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o ColorOption currCO = index.data(Qt::UserRole).value(); + QFontMetrics fm = QFontMetrics(painter->font()); int penWidth = painter->pen().width(); - int fontHeight = painter->fontMetrics().height(); + int fontHeight = fm.height(); QPoint tl = option.rect.topLeft(); QRect optionNameRect; @@ -126,9 +127,9 @@ void ColorOptionDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o painter->setPen(qApp->palette().text().color()); - QString name = - painter->fontMetrics().elidedText(optionInfoMap__[currCO.optionName].displayingtext, - Qt::ElideRight, optionNameRect.width()); + QFontMetrics fm2 = QFontMetrics(painter->font()); + QString name = fm2.elidedText(optionInfoMap__[currCO.optionName].displayingtext, + Qt::ElideRight, optionNameRect.width()); painter->drawText(optionNameRect, name); QPainterPath roundedOptionRect; @@ -155,7 +156,8 @@ void ColorOptionDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o painter->setPen(currCO.color); painter->fillPath(roundedColorRect, currCO.color); - QString desc = painter->fontMetrics().elidedText( + QFontMetrics fm3 = QFontMetrics(painter->font()); + QString desc = fm3.elidedText( currCO.optionName + ": " + optionInfoMap__[currCO.optionName].info, Qt::ElideRight, descTextRect.width()); painter->setPen(qApp->palette().text().color()); @@ -197,7 +199,8 @@ QPixmap ColorOptionDelegate::getPixmapFromSvg(const QString &fileName, const QCo data.replace(QRegularExpression("#[0-9a-fA-F]{6}"), QString("%1").arg(after.name())); QSvgRenderer svgRenderer(data.toUtf8()); - QPixmap pix(QSize(qApp->fontMetrics().height(), qApp->fontMetrics().height())); + QFontMetrics fm = QFontMetrics(qApp->font()); + QPixmap pix(QSize(fm.height(), fm.height())); pix.fill(Qt::transparent); QPainter pixPainter(&pix); diff --git a/src/widgets/DisassemblyWidget.cpp b/src/widgets/DisassemblyWidget.cpp index be8c9bc9..4c18b57f 100644 --- a/src/widgets/DisassemblyWidget.cpp +++ b/src/widgets/DisassemblyWidget.cpp @@ -211,7 +211,7 @@ QString DisassemblyWidget::getWidgetType() QFontMetrics DisassemblyWidget::getFontMetrics() { - return mDisasTextEdit->fontMetrics(); + return QFontMetrics(mDisasTextEdit->font()); } QList DisassemblyWidget::getLines() diff --git a/src/widgets/SimpleTextGraphView.cpp b/src/widgets/SimpleTextGraphView.cpp index feebdd4c..c98c8fcf 100644 --- a/src/widgets/SimpleTextGraphView.cpp +++ b/src/widgets/SimpleTextGraphView.cpp @@ -119,8 +119,9 @@ void SimpleTextGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block, b p.setPen(palette().color(QPalette::WindowText)); // Render node text + QFontMetrics fm = QFontMetrics(p.font()); auto x = block.x + padding / 2; - int y = block.y + padding / 2 + p.fontMetrics().ascent(); + int y = block.y + padding / 2 + fm.ascent(); p.drawText(QPoint(x, y), content.text); } From fdb2f9e545e46afee784e1662c428e86e4b44ef5 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Tue, 7 Feb 2023 21:28:57 +0800 Subject: [PATCH 63/77] Add Qt6 CI job --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38f9b70f..4a2dfe27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: name: [ linux-x86_64, linux-x86_64-system-deps, + linux-x86_64-qt6-system-deps, macos-x86_64, windows-x86_64, tarball @@ -39,6 +40,12 @@ jobs: system-deps: true cc-override: '/usr/bin/gcc-7' cxx-override: '/usr/bin/g++-7' + - name: linux-x86_64-qt6-system-deps # ensure that Cutter can be built at least in basic config on Ubuntu 22.04 using sytem libraries + os: ubuntu-22.04 + python-version: 3.10.x + system-deps: true + cc-override: '/usr/bin/gcc-12' + cxx-override: '/usr/bin/g++-12' - name: linux-x86_64 os: ubuntu-18.04 python-version: 3.7.x @@ -69,16 +76,21 @@ jobs: if: contains(matrix.os, 'ubuntu') run: | sudo apt-get update - sudo apt-get install libgraphviz-dev mesa-common-dev libxkbcommon-x11-dev libclang-8-dev llvm-8 ninja-build - if [[ "${{ matrix.os }}" = "ubuntu-18.04" ]] + sudo apt-get install libgraphviz-dev mesa-common-dev libxkbcommon-x11-dev ninja-build + if [[ "${{ matrix.os }}" = "ubuntu-18.04" || "${{ matrix.os }}" = "ubuntu-20.04" ]] then # install additional packages needed for appimage - sudo apt-get install libxcb1-dev libxkbcommon-dev libxcb-*-dev libegl1 + sudo apt-get install libxcb1-dev libxkbcommon-dev libxcb-*-dev libegl1 libclang-8-dev llvm-8 fi - if [[ "${{ matrix.system-deps }}" = "true" ]] + if [[ "${{ matrix.os }}" = "ubuntu-18.04" && "${{ matrix.system-deps }}" = "true" ]] then sudo apt-get install qt5-default libqt5svg5-dev qttools5-dev qttools5-dev-tools fi + if [[ "${{ matrix.os }}" = "ubuntu-22.04" ]] + then + sudo apt-get install libclang-12-dev llvm-12 qt6-base-dev qt6-tools-dev \ + qt6-tools-dev-tools libqt6svg6-dev libqt6core5compat6-dev libqt6svgwidgets6 qt6-l10n-tools + fi - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -146,6 +158,14 @@ jobs: -DCMAKE_INSTALL_PREFIX=appdir/usr \ -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON \ .. + elif [[ "${{ matrix.os }}" = "ubuntu-22.04" ]] + then + cmake \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCUTTER_QT6=ON \ + -DCUTTER_USE_BUNDLED_RIZIN=ON \ + .. else cmake \ -G Ninja \ From 957364dedfa83a5bc14389abdb2bbe223d61a023 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Thu, 9 Feb 2023 00:25:55 +0800 Subject: [PATCH 64/77] Update rizin to the latest "stable" (#3107) --- rizin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rizin b/rizin index b740cba3..db6ff0d1 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit b740cba35d43327109ed238fa5562edf01d619c2 +Subproject commit db6ff0d1cd813f452462d8e4b9e0351b6a9cb09e From 6397a2ec6458e051bc4553ca24c02f68cae3c7bc Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Thu, 9 Feb 2023 15:00:37 +0800 Subject: [PATCH 65/77] Update rizin to the latest "stable" (#3112) --- rizin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rizin b/rizin index db6ff0d1..3559b43f 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit db6ff0d1cd813f452462d8e4b9e0351b6a9cb09e +Subproject commit 3559b43f2ffc67e7a5031b7343690c2d596c5afc From d3790af78ee46c9b07ad741d5449cb0d463b1f66 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Wed, 15 Feb 2023 02:17:31 +0800 Subject: [PATCH 66/77] Update rizin to the latest "stable" (#3120) --- rizin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rizin b/rizin index 3559b43f..fde96842 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit 3559b43f2ffc67e7a5031b7343690c2d596c5afc +Subproject commit fde968429f6ab861278fc756caceb7e5e4e89d81 From 235b75f3ed1e2f1310bef889256188579777eced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Wed, 15 Feb 2023 15:58:45 +0100 Subject: [PATCH 67/77] Update translations (#3122) --- src/translations | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations b/src/translations index 97429865..e4715bec 160000 --- a/src/translations +++ b/src/translations @@ -1 +1 @@ -Subproject commit 974298653ba71b958e1b6c83f6011f5fefff6236 +Subproject commit e4715bece7d9bff451b188d9bc1dea7eaf50264a From f2e00c59c813170ee8af0ee95fb18bccb0814c33 Mon Sep 17 00:00:00 2001 From: wargio Date: Sat, 18 Feb 2023 21:12:27 +0800 Subject: [PATCH 68/77] Fix #3113 - Wrong usage of rz_cmd_call instead of rz_core_cmd and @! --- src/core/Cutter.cpp | 5 ++--- src/widgets/HexdumpWidget.cpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index dc585dd4..8a721109 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -475,8 +475,8 @@ QString CutterCore::cmdRaw(const char *cmd) CORE_LOCK(); rz_cons_push(); - // rz_cmd_call does not return the output of the command - rz_cmd_call(core->rcmd, cmd); + // rz_core_cmd does not return the output of the command + rz_core_cmd(core, cmd, 0); // we grab the output straight from rz_cons res = rz_cons_get_buffer(); @@ -4494,7 +4494,6 @@ QByteArray CutterCore::ioRead(RVA addr, int len) /* Zero-copy */ array.resize(len); if (!rz_io_read_at(core->io, addr, (uint8_t *)array.data(), len)) { - qWarning() << "Can't read data" << addr << len; array.fill(0xff); } diff --git a/src/widgets/HexdumpWidget.cpp b/src/widgets/HexdumpWidget.cpp index e6a088c4..7b5d1acd 100644 --- a/src/widgets/HexdumpWidget.cpp +++ b/src/widgets/HexdumpWidget.cpp @@ -240,7 +240,7 @@ void HexdumpWidget::updateParseWindow(RVA start_address, int size) ui->hexDisasTextEdit->setPlainText( selectedCommand != "" ? Core()->cmdRawAt( - QString("%1 %2").arg(selectedCommand).arg(size), start_address) + QString("%1 @! %2").arg(selectedCommand).arg(size), start_address) : ""); } else { // Fill the information tab hashes and entropy From eb0327ab0c9237d18e036e499d643fe6b65f6345 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Sat, 18 Feb 2023 10:49:32 +0800 Subject: [PATCH 69/77] Update rizin to the latest "stable" --- rizin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rizin b/rizin index fde96842..a7d643ad 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit fde968429f6ab861278fc756caceb7e5e4e89d81 +Subproject commit a7d643ad4bd969f8bc51e90e97c9926330fd53ff From 7057c8606f18f059dfdb3417b216537b182c1fc8 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Sat, 18 Feb 2023 10:50:20 +0800 Subject: [PATCH 70/77] Update Russian and Chinese translations --- src/translations | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations b/src/translations index e4715bec..41c0c778 160000 --- a/src/translations +++ b/src/translations @@ -1 +1 @@ -Subproject commit e4715bece7d9bff451b188d9bc1dea7eaf50264a +Subproject commit 41c0c778b942577749ea2fed117e48a2cf3892df From 20801f7fe6bb77f72ebca5eae7efd42ea8c9f7f7 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Wed, 8 Feb 2023 21:02:00 +0800 Subject: [PATCH 71/77] Enable graphviz for AppImage --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a2dfe27..757189d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,7 +145,7 @@ jobs: -DPYTHON_INCLUDE_DIR="$CUTTER_DEPS_PYTHON_PREFIX/include/python3.9" \ -DPYTHON_EXECUTABLE="$CUTTER_DEPS_PYTHON_PREFIX/bin/python3" \ -DCUTTER_ENABLE_PYTHON_BINDINGS=ON \ - -DCUTTER_ENABLE_GRAPHVIZ=OFF \ + -DCUTTER_ENABLE_GRAPHVIZ=ON \ -DCUTTER_USE_BUNDLED_RIZIN=ON \ -DCUTTER_APPIMAGE_BUILD=ON \ -DCUTTER_ENABLE_PACKAGING=ON \ From 7a96fad5469d1dd63882923c9b3d398973a9b18c Mon Sep 17 00:00:00 2001 From: wargio Date: Sat, 18 Feb 2023 13:35:49 +0800 Subject: [PATCH 72/77] Fix graph export commands to use c api --- src/core/Cutter.cpp | 65 +++++++++++++++ src/core/Cutter.h | 19 +++++ src/widgets/CallGraph.cpp | 7 +- src/widgets/CutterGraphView.cpp | 114 +++++++++++++------------- src/widgets/CutterGraphView.h | 44 ++++------ src/widgets/DisassemblerGraphView.cpp | 8 +- 6 files changed, 168 insertions(+), 89 deletions(-) diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 8a721109..72b8333e 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -4515,3 +4515,68 @@ QStringList CutterCore::getConfigVariableSpaces(const QString &key) rz_list_free(list); return stringList; } + +char *CutterCore::getTextualGraphAt(RzCoreGraphType type, RzCoreGraphFormat format, RVA address) +{ + CORE_LOCK(); + char *string = nullptr; + RzGraph *graph = rz_core_graph(core, type, address); + if (!graph) { + if (address == RVA_INVALID) { + qWarning() << "Cannot get global graph"; + } else { + qWarning() << "Cannot get graph at " << RzAddressString(address); + } + return nullptr; + } + core->graph->is_callgraph = type == RZ_CORE_GRAPH_TYPE_FUNCALL; + + switch (format) { + case RZ_CORE_GRAPH_FORMAT_CMD: { + string = rz_graph_drawable_to_cmd(graph); + break; + } + case RZ_CORE_GRAPH_FORMAT_DOT: { + string = rz_core_graph_to_dot_str(core, graph); + break; + } + case RZ_CORE_GRAPH_FORMAT_JSON: + /* fall-thru */ + case RZ_CORE_GRAPH_FORMAT_JSON_DISASM: { + string = rz_graph_drawable_to_json_str(graph, true); + break; + } + case RZ_CORE_GRAPH_FORMAT_GML: { + string = rz_graph_drawable_to_gml(graph); + break; + } + default: + break; + } + rz_graph_free(graph); + + if (!string) { + qWarning() << "Failed to generate graph"; + } + + return string; +} + +void CutterCore::writeGraphvizGraphToFile(QString path, QString format, RzCoreGraphType type, + RVA address) +{ + TempConfig tempConfig; + tempConfig.set("scr.color", false); + tempConfig.set("graph.gv.format", format); + + CORE_LOCK(); + auto filepath = path.toUtf8(); + + if (!rz_core_graph_write(core, address, type, filepath)) { + if (address == RVA_INVALID) { + qWarning() << "Cannot get global graph"; + } else { + qWarning() << "Cannot get graph at " << RzAddressString(address); + } + } +} \ No newline at end of file diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 7d8aa098..5138807d 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -720,6 +720,25 @@ public: */ bool isWriteModeEnabled(); + /** + * @brief Returns the textual version of global or specific graph. + * @param type Graph type, example RZ_CORE_GRAPH_TYPE_FUNCALL or RZ_CORE_GRAPH_TYPE_IMPORT + * @param format Graph format, example RZ_CORE_GRAPH_FORMAT_DOT or RZ_CORE_GRAPH_FORMAT_GML + * @param address The object address (if global set it to RVA_INVALID) + * @return The textual graph string. + */ + char *getTextualGraphAt(RzCoreGraphType type, RzCoreGraphFormat format, RVA address); + + /** + * @brief Writes a graphviz graph to a file. + * @param path The file output path + * @param format The output format (see graph.gv.format) + * @param type The graph type, example RZ_CORE_GRAPH_TYPE_FUNCALL or + * RZ_CORE_GRAPH_TYPE_IMPORT + * @param address The object address (if global set it to RVA_INVALID) + */ + void writeGraphvizGraphToFile(QString path, QString format, RzCoreGraphType type, RVA address); + signals: void refreshAll(); diff --git a/src/widgets/CallGraph.cpp b/src/widgets/CallGraph.cpp index 001844d0..9836a561 100644 --- a/src/widgets/CallGraph.cpp +++ b/src/widgets/CallGraph.cpp @@ -7,7 +7,9 @@ #include CallGraphWidget::CallGraphWidget(MainWindow *main, bool global) - : MemoryDockWidget(MemoryWidgetType::CallGraph, main), graphView(new CallGraphView(this, main, global)), global(global) + : MemoryDockWidget(MemoryWidgetType::CallGraph, main), + graphView(new CallGraphView(this, main, global)), + global(global) { setObjectName(main ? main->getUniqueObjectName(getWidgetType()) : getWidgetType()); this->setWindowTitle(getWindowTitle()); @@ -53,7 +55,7 @@ void CallGraphView::showExportDialog() } else { defaultName = QString("callgraph_%1").arg(RzAddressString(address)); } - showExportGraphDialog(defaultName, global ? "agC" : "agc", address); + showExportGraphDialog(defaultName, RZ_CORE_GRAPH_TYPE_FUNCALL, global ? RVA_INVALID : address); } void CallGraphView::showAddress(RVA address) @@ -80,7 +82,6 @@ static inline bool isBetween(ut64 a, ut64 x, ut64 b) return (a == UT64_MAX || a <= x) && (b == UT64_MAX || x <= b); } - void CallGraphView::loadCurrentGraph() { blockContent.clear(); diff --git a/src/widgets/CutterGraphView.cpp b/src/widgets/CutterGraphView.cpp index 521bde32..8d89f964 100644 --- a/src/widgets/CutterGraphView.cpp +++ b/src/widgets/CutterGraphView.cpp @@ -152,7 +152,7 @@ void CutterGraphView::zoomReset() void CutterGraphView::showExportDialog() { - showExportGraphDialog("graph", "", RVA_INVALID); + showExportGraphDialog("global_funcall", RZ_CORE_GRAPH_TYPE_FUNCALL, RVA_INVALID); } void CutterGraphView::updateColors() @@ -318,12 +318,12 @@ void CutterGraphView::mouseMoveEvent(QMouseEvent *event) emit graphMoved(); } -void CutterGraphView::exportGraph(QString filePath, GraphExportType type, QString graphCommand, - RVA address) +void CutterGraphView::exportGraph(QString filePath, GraphExportType exportType, + RzCoreGraphType graphType, RVA address) { bool graphTransparent = Config()->getBitmapTransparentState(); double graphScaleFactor = Config()->getBitmapExportScaleFactor(); - switch (type) { + switch (exportType) { case GraphExportType::Png: this->saveAsBitmap(filePath, "png", graphScaleFactor, graphTransparent); break; @@ -335,56 +335,55 @@ void CutterGraphView::exportGraph(QString filePath, GraphExportType type, QStrin break; case GraphExportType::GVDot: - exportRzTextGraph(filePath, graphCommand + "d", address); + exportRzTextGraph(filePath, graphType, RZ_CORE_GRAPH_FORMAT_DOT, address); break; case GraphExportType::RzJson: - exportRzTextGraph(filePath, graphCommand + "j", address); + exportRzTextGraph(filePath, graphType, RZ_CORE_GRAPH_FORMAT_JSON, address); break; case GraphExportType::RzGml: - exportRzTextGraph(filePath, graphCommand + "g", address); - break; - case GraphExportType::RzSDBKeyValue: - exportRzTextGraph(filePath, graphCommand + "k", address); + exportRzTextGraph(filePath, graphType, RZ_CORE_GRAPH_FORMAT_GML, address); break; case GraphExportType::GVJson: - exportRizinGraphvizGraph(filePath, "json", graphCommand, address); + Core()->writeGraphvizGraphToFile(filePath, "json", graphType, address); break; case GraphExportType::GVGif: - exportRizinGraphvizGraph(filePath, "gif", graphCommand, address); + Core()->writeGraphvizGraphToFile(filePath, "gif", graphType, address); break; case GraphExportType::GVPng: - exportRizinGraphvizGraph(filePath, "png", graphCommand, address); + Core()->writeGraphvizGraphToFile(filePath, "png", graphType, address); break; case GraphExportType::GVJpeg: - exportRizinGraphvizGraph(filePath, "jpg", graphCommand, address); + Core()->writeGraphvizGraphToFile(filePath, "jpg", graphType, address); break; case GraphExportType::GVPostScript: - exportRizinGraphvizGraph(filePath, "ps", graphCommand, address); + Core()->writeGraphvizGraphToFile(filePath, "ps", graphType, address); break; case GraphExportType::GVSvg: - exportRizinGraphvizGraph(filePath, "svg", graphCommand, address); + Core()->writeGraphvizGraphToFile(filePath, "svg", graphType, address); + break; + case GraphExportType::GVPdf: + Core()->writeGraphvizGraphToFile(filePath, "pdf", graphType, address); break; } } -void CutterGraphView::exportRizinGraphvizGraph(QString filePath, QString type, QString graphCommand, - RVA address) +void CutterGraphView::exportRzTextGraph(QString filePath, RzCoreGraphType type, + RzCoreGraphFormat format, RVA address) { - TempConfig tempConfig; - tempConfig.set("graph.gv.format", type); - qWarning() << Core()->cmdRawAt(QString("%0w \"%1\"").arg(graphCommand).arg(filePath), address); -} - -void CutterGraphView::exportRzTextGraph(QString filePath, QString graphCommand, RVA address) -{ - QFile file(filePath); - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - qWarning() << "Can't open file"; + char *string = Core()->getTextualGraphAt(type, format, address); + if (!string) { return; } - QTextStream fileOut(&file); - fileOut << Core()->cmdRawAt(QString("%0").arg(graphCommand), address); + + QFile file(filePath); + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream fileOut(&file); + fileOut << string; + } else { + qWarning() << "Can't open or create file: " << filePath; + } + free(string); } bool CutterGraphView::graphIsBitamp(CutterGraphView::GraphExportType type) @@ -403,40 +402,39 @@ bool CutterGraphView::graphIsBitamp(CutterGraphView::GraphExportType type) Q_DECLARE_METATYPE(CutterGraphView::GraphExportType); -void CutterGraphView::showExportGraphDialog(QString defaultName, QString graphCommand, RVA address) +void CutterGraphView::showExportGraphDialog(QString defaultName, RzCoreGraphType type, RVA address) { + qWarning() << defaultName << " - " << type << " addr " << RzAddressString(address); QVector types = { { tr("PNG (*.png)"), "png", QVariant::fromValue(GraphExportType::Png) }, { tr("JPEG (*.jpg)"), "jpg", QVariant::fromValue(GraphExportType::Jpeg) }, { tr("SVG (*.svg)"), "svg", QVariant::fromValue(GraphExportType::Svg) } }; - bool rzGraphExports = !graphCommand.isEmpty(); - if (rzGraphExports) { - types.append({ - { tr("Graphviz dot (*.dot)"), "dot", QVariant::fromValue(GraphExportType::GVDot) }, - { tr("Graph Modelling Language (*.gml)"), "gml", - QVariant::fromValue(GraphExportType::RzGml) }, - { tr("RZ JSON (*.json)"), "json", QVariant::fromValue(GraphExportType::RzJson) }, - { tr("SDB key-value (*.txt)"), "txt", - QVariant::fromValue(GraphExportType::RzSDBKeyValue) }, - }); - bool hasGraphviz = !QStandardPaths::findExecutable("dot").isEmpty() - || !QStandardPaths::findExecutable("xdot").isEmpty(); - if (hasGraphviz) { - types.append({ { tr("Graphviz json (*.json)"), "json", - QVariant::fromValue(GraphExportType::GVJson) }, - { tr("Graphviz gif (*.gif)"), "gif", - QVariant::fromValue(GraphExportType::GVGif) }, - { tr("Graphviz png (*.png)"), "png", - QVariant::fromValue(GraphExportType::GVPng) }, - { tr("Graphviz jpg (*.jpg)"), "jpg", - QVariant::fromValue(GraphExportType::GVJpeg) }, - { tr("Graphviz PostScript (*.ps)"), "ps", - QVariant::fromValue(GraphExportType::GVPostScript) }, - { tr("Graphviz svg (*.svg)"), "svg", - QVariant::fromValue(GraphExportType::GVSvg) } }); - } + types.append({ + { tr("Graphviz dot (*.dot)"), "dot", QVariant::fromValue(GraphExportType::GVDot) }, + { tr("Graph Modelling Language (*.gml)"), "gml", + QVariant::fromValue(GraphExportType::RzGml) }, + { tr("RZ JSON (*.json)"), "json", QVariant::fromValue(GraphExportType::RzJson) }, + }); + + bool hasGraphviz = !QStandardPaths::findExecutable("dot").isEmpty() + || !QStandardPaths::findExecutable("xdot").isEmpty(); + if (hasGraphviz) { + types.append({ { tr("Graphviz json (*.json)"), "json", + QVariant::fromValue(GraphExportType::GVJson) }, + { tr("Graphviz gif (*.gif)"), "gif", + QVariant::fromValue(GraphExportType::GVGif) }, + { tr("Graphviz png (*.png)"), "png", + QVariant::fromValue(GraphExportType::GVPng) }, + { tr("Graphviz jpg (*.jpg)"), "jpg", + QVariant::fromValue(GraphExportType::GVJpeg) }, + { tr("Graphviz PostScript (*.ps)"), "ps", + QVariant::fromValue(GraphExportType::GVPostScript) }, + { tr("Graphviz svg (*.svg)"), "svg", + QVariant::fromValue(GraphExportType::GVSvg) }, + { tr("Graphviz pdf (*.pdf)"), "pdf", + QVariant::fromValue(GraphExportType::GVPdf) } }); } MultitypeFileSaveDialog dialog(this, tr("Export Graph")); @@ -470,5 +468,5 @@ void CutterGraphView::showExportGraphDialog(QString defaultName, QString graphCo } QString filePath = dialog.selectedFiles().first(); - exportGraph(filePath, exportType, graphCommand, address); + exportGraph(filePath, exportType, type, address); } diff --git a/src/widgets/CutterGraphView.h b/src/widgets/CutterGraphView.h index 5a879908..36958743 100644 --- a/src/widgets/CutterGraphView.h +++ b/src/widgets/CutterGraphView.h @@ -32,48 +32,38 @@ public: GVJpeg, GVPostScript, GVSvg, + GVPdf, RzGml, - RzSDBKeyValue, RzJson }; /** * @brief Export graph to a file in the specified format - * @param filePath - * @param type export type, GV* and Rz* types require \p graphCommand - * @param graphCommand rizin graph printing command without type, not required for direct image - * export - * @param address object address for commands like agf + * @param filePath - output file path + * @param exportType - export type, GV* and Rz* types require \p graphCommand + * @param graphType - graph type, example RZ_CORE_GRAPH_TYPE_FUNCALL or + * RZ_CORE_GRAPH_TYPE_IMPORT + * @param address - object address (if global set it to RVA_INVALID) */ - void exportGraph(QString filePath, GraphExportType type, QString graphCommand = "", + void exportGraph(QString filePath, GraphExportType exportType, RzCoreGraphType graphType, RVA address = RVA_INVALID); - /** - * @brief Export image using rizin ag*w command and graphviz. - * Requires graphviz dot executable in the path. - * - * @param filePath output file path - * @param type image format as expected by "e graph.gv.format" - * @param graphCommand rizin command without type, for example agf - * @param address object address if required by command - */ - void exportRizinGraphvizGraph(QString filePath, QString type, QString graphCommand, - RVA address); + /** * @brief Export graph in one of the text formats supported by rizin json, gml, SDB key-value - * @param filePath output file path - * @param graphCommand graph command including the format, example "agfd" or "agfg" - * @param address object address if required by command + * @param filePath - output file path + * @param type - graph type, example RZ_CORE_GRAPH_TYPE_FUNCALL or RZ_CORE_GRAPH_TYPE_IMPORT + * @param format - graph format, example RZ_CORE_GRAPH_FORMAT_DOT or RZ_CORE_GRAPH_FORMAT_GML + * @param address - object address (if global set it to RVA_INVALID) */ - void exportRzTextGraph(QString filePath, QString graphCommand, RVA address); + void exportRzTextGraph(QString filePath, RzCoreGraphType type, RzCoreGraphFormat format, + RVA address); static bool graphIsBitamp(GraphExportType type); /** * @brief Show graph export dialog. * @param defaultName - default file name in the export dialog - * @param graphCommand - rizin graph commmand with graph type and without export type, for - * example afC. Leave empty for non-rizin graphs. In such case only direct image export will be - * available. - * @param address - object address if relevant for \p graphCommand + * @param type - graph type, example RZ_CORE_GRAPH_TYPE_FUNCALL or RZ_CORE_GRAPH_TYPE_IMPORT + * @param address - object address (if global set it to RVA_INVALID) */ - void showExportGraphDialog(QString defaultName, QString graphCommand = "", + void showExportGraphDialog(QString defaultName, RzCoreGraphType type, RVA address = RVA_INVALID); public slots: diff --git a/src/widgets/DisassemblerGraphView.cpp b/src/widgets/DisassemblerGraphView.cpp index 008e8fd5..69f87e5b 100644 --- a/src/widgets/DisassemblerGraphView.cpp +++ b/src/widgets/DisassemblerGraphView.cpp @@ -194,6 +194,7 @@ void DisassemblerGraphView::loadCurrentGraph() windowTitle = tr("Graph"); if (fcn && RZ_STR_ISNOTEMPTY(fcn->name)) { + currentFcnAddr = fcn->addr; auto fcnName = fromOwned(rz_str_escape_utf8_for_json(fcn->name, -1)); windowTitle += QString("(%0)").arg(fcnName.get()); } else { @@ -891,6 +892,11 @@ void DisassemblerGraphView::contextMenuEvent(QContextMenuEvent *event) void DisassemblerGraphView::showExportDialog() { + if (currentFcnAddr == RVA_INVALID) { + qWarning() << "Cannot find current function."; + return; + } + QString defaultName = "graph"; if (auto f = Core()->functionIn(currentFcnAddr)) { QString functionName = f->name; @@ -901,7 +907,7 @@ void DisassemblerGraphView::showExportDialog() defaultName = functionName; } } - showExportGraphDialog(defaultName, "agf", currentFcnAddr); + showExportGraphDialog(defaultName, RZ_CORE_GRAPH_TYPE_BLOCK_FUN, currentFcnAddr); } void DisassemblerGraphView::blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event, From b98eb64646f5a570ce9e899b1d779da785f7208b Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Mon, 20 Feb 2023 15:07:27 +0800 Subject: [PATCH 73/77] Remove `pci` hexdump parsing mode --- src/widgets/HexdumpWidget.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widgets/HexdumpWidget.cpp b/src/widgets/HexdumpWidget.cpp index 7b5d1acd..e772da5f 100644 --- a/src/widgets/HexdumpWidget.cpp +++ b/src/widgets/HexdumpWidget.cpp @@ -144,7 +144,6 @@ void HexdumpWidget::initParsing() ui->parseTypeComboBox->addItem(tr("String"), "pcs"); ui->parseTypeComboBox->addItem(tr("Assembler"), "pca"); ui->parseTypeComboBox->addItem(tr("C bytes"), "pc"); - ui->parseTypeComboBox->addItem(tr("C bytes with instructions"), "pci"); ui->parseTypeComboBox->addItem(tr("C half-words (2 byte)"), "pch"); ui->parseTypeComboBox->addItem(tr("C words (4 byte)"), "pcw"); ui->parseTypeComboBox->addItem(tr("C dwords (8 byte)"), "pcd"); From 54e8e9c40318b7a540fefd9c738e4879342c1d24 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Mon, 20 Feb 2023 17:48:44 +0800 Subject: [PATCH 74/77] Remove LGTM badge --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index bdd37783..d64e349a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ Cutter is a free and open-source reverse engineering platform powered by [rizin] [![Cutter CI](https://github.com/rizinorg/cutter/workflows/Cutter%20CI/badge.svg)](https://github.com/rizinorg/cutter/actions?query=workflow%3A%22Cutter+CI%22) [![Build status](https://ci.appveyor.com/api/projects/status/tn7kttv55b8wf799/branch/dev?svg=true)](https://ci.appveyor.com/project/rizinorg/cutter/branch/dev) -[![Total alerts](https://img.shields.io/lgtm/alerts/g/rizinorg/cutter.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/rizinorg/cutter/alerts/) ![Screenshot](https://raw.githubusercontent.com/rizinorg/cutter/dev/docs/source/images/screenshot.png) From 4701cedfd2fc0cf3229d5f83adbc87aae7f7c906 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Tue, 21 Feb 2023 15:17:01 +0800 Subject: [PATCH 75/77] Get type format from RzBaseType directly --- src/core/Cutter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 72b8333e..dad33c8e 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -3850,7 +3850,7 @@ QList CutterCore::getBaseType(RzBaseTypeKind kind, const char * exp.type = type->name; exp.size = rz_type_db_base_get_bitsize(core->analysis->typedb, type); - exp.format = rz_type_format(core->analysis->typedb, type->name); + exp.format = rz_base_type_as_format(core->analysis->typedb, type); exp.category = tr(category); types << exp; } From d39a282ea70164c7afa4d90f466935d81367afd6 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Tue, 21 Feb 2023 21:32:01 +0800 Subject: [PATCH 76/77] Update Rizin to the latest stable --- rizin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rizin b/rizin index a7d643ad..12898a36 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit a7d643ad4bd969f8bc51e90e97c9926330fd53ff +Subproject commit 12898a365e70c22892d78abdd2627e7269533c5f From c68598757de4b2e86fb448c7c082022ffba3b7df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Nohlg=C3=A5rd?= Date: Wed, 22 Feb 2023 02:06:32 +0100 Subject: [PATCH 77/77] Fix typo in CMake option names (#3137) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce66a7a8..aeb35127 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,8 +25,8 @@ option(CUTTER_ENABLE_PACKAGING "Enable building platform-specific packages for d option(CUTTER_ENABLE_SIGDB "Downloads and installs sigdb (only available when CUTTER_USE_BUNDLED_RIZIN=ON)." OFF) option(CUTTER_PACKAGE_DEPENDENCIES "During install step include the third party dependencies." OFF) option(CUTTER_PACKAGE_RZ_GHIDRA "Compile and install rz-ghidra during install step." OFF) -option(CUTTER_PACKAGE_RZ_LIBSWIFT, "Compile and install rz-libswift demangler during the install step." OFF) -option(CUTTER_PACKAGE_RZ_LIBYARA, "Compile and install rz-libyara during the install step." OFF) +option(CUTTER_PACKAGE_RZ_LIBSWIFT "Compile and install rz-libswift demangler during the install step." OFF) +option(CUTTER_PACKAGE_RZ_LIBYARA "Compile and install rz-libyara during the install step." OFF) option(CUTTER_PACKAGE_JSDEC "Compile and install jsdec during install step." OFF) OPTION(CUTTER_QT6 "Use QT6" OFF)