Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ set("CUTTER_QT" 6 CACHE STRING "Major QT version to use 5|6")
set_property(CACHE "CUTTER_QT" PROPERTY STRINGS 5 6)
option(CUTTER_INCLUDE_GIT_HASH "Include git hash in full version" ON)
set(CUTTER_VERSION_SUFFIX "" CACHE STRING "Can be used by packagers to differentiate multiple packages using same source, for example build number or presence of additional patches.")
option(CUTTER_ASAN "Build with ASAN" OFF)

if(NOT CUTTER_ENABLE_PYTHON)
set(CUTTER_ENABLE_PYTHON_BINDINGS OFF)
Expand Down Expand Up @@ -186,11 +187,13 @@ message(STATUS "- Package RzSilhouette: ${CUTTER_PACKAGE_RZ_SILHOUETTE}")
message(STATUS "- Package JSDec: ${CUTTER_PACKAGE_JSDEC}")
message(STATUS "- QT: ${CUTTER_QT}")
message(STATUS "")
message(STATUS "- ASAN: ${CUTTER_ASAN}")

if (CUTTER_QT LESS 5 OR CUTTER_QT GREATER 6)
message(FATAL_ERROR "Unsupported QT major version")
endif()


add_subdirectory(src)

if(CUTTER_ENABLE_PACKAGING)
Expand Down
7 changes: 6 additions & 1 deletion cmake/BundledRizin.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ if (CUTTER_ENABLE_SIGDB)
list(APPEND MESON_OPTIONS "-Dinstall_sigdb=true")
endif()

# Enable ASan/UBSan in bundled Rizin when Cutter ASAN is enabled
if(CUTTER_ASAN)
list(APPEND MESON_OPTIONS "-Db_sanitize=address,undefined")
endif()

find_program(MESON meson)
if(NOT MESON)
message(FATAL_ERROR "Failed to find meson, which is required to build bundled rizin")
Expand Down Expand Up @@ -85,4 +90,4 @@ if (WIN32)
else ()
install(DIRECTORY "${RIZIN_INSTALL_DIR}/" DESTINATION "." USE_SOURCE_PERMISSIONS
PATTERN "rz-test" EXCLUDE)
endif()
endif()
28 changes: 26 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ set(SOURCES
dialogs/preferences/RizinConfigOptionsWidget.cpp
dialogs/IntervalDialog.cpp
dialogs/StringListDialog.cpp
tools/bindiff/DiffLoadDialog.cpp
core/BinDiff.cpp
core/CutterDiff.cpp
tools/bindiff/CutterDiffWindow.cpp
tools/bindiff/DiffWaitDialog.cpp
tools/bindiff/HexDiff.cpp
tools/bindiff/LineDiffWidget.cpp
)
set(HEADER_FILES
core/Cutter.h
Expand Down Expand Up @@ -356,6 +363,13 @@ set(HEADER_FILES
dialogs/preferences/RizinConfigOptionsWidget.h
dialogs/IntervalDialog.h
dialogs/StringListDialog.h
tools/bindiff/DiffLoadDialog.h
core/BinDiff.h
core/CutterDiff.h
tools/bindiff/CutterDiffWindow.h
tools/bindiff/DiffWaitDialog.h
tools/bindiff/HexDiff.h
tools/bindiff/LineDiffWidget.h
)
set(UI_FILES
dialogs/AboutDialog.ui
Expand Down Expand Up @@ -440,6 +454,10 @@ set(UI_FILES
dialogs/preferences/SymbolsOptionsWidget.ui
dialogs/preferences/InterfaceOptionsWidget.ui
dialogs/preferences/RizinConfigOptionsWidget.ui
tools/bindiff/DiffLoadDialog.ui
tools/bindiff/CutterDiffWindow.ui
tools/bindiff/DiffWaitDialog.ui
tools/bindiff/LineDiffWidget.ui
)
set(QRC_FILES
resources.qrc
Expand Down Expand Up @@ -592,6 +610,12 @@ if(CUTTER_ENABLE_PYTHON)
endif()
endif()

if(CUTTER_ASAN)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always -fsanitize=address,undefined -fno-omit-frame-pointer -fsanitize-address-use-after-scope")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always -fsanitize=address,undefined -fno-omit-frame-pointer -fsanitize-address-use-after-scope")
set(CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} -fdiagnostics-color=always -fsanitize=address,undefined -fno-omit-frame-pointer -fsanitize-address-use-after-scope")
endif()

if(TARGET KF${CUTTER_QT}::SyntaxHighlighting)
target_link_libraries(Cutter PRIVATE KF${CUTTER_QT}::SyntaxHighlighting)
target_compile_definitions(Cutter PRIVATE CUTTER_ENABLE_KSYNTAXHIGHLIGHTING)
Expand All @@ -602,11 +626,11 @@ if (CUTTER_APPIMAGE_BUILD)
endif()

if (CUTTER_PACKAGE_JSDEC)
target_compile_definitions(Cutter PRIVATE CUTTER_BUNDLE_JSDEC)
target_compile_definitions(Cutter PRIVATE CUTTER_BUNDLE_JSDEC)
endif()

if(APPLE AND CUTTER_ENABLE_PACKAGING AND CUTTER_USE_BUNDLED_RIZIN)
target_compile_definitions(Cutter PRIVATE MACOS_RZ_BUNDLED)
target_compile_definitions(Cutter PRIVATE MACOS_RZ_BUNDLED)
endif()

if(CUTTER_ENABLE_PACKAGING)
Expand Down
210 changes: 210 additions & 0 deletions src/core/BinDiff.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#include "BinDiff.h"

bool BinDiff::threadCallback(const size_t nLeft, const size_t nMatch, void *user)
{
auto bdiff = reinterpret_cast<BinDiff *>(user);
return bdiff->updateProgress(nLeft, nMatch);
}

BinDiff::BinDiff()
: result(nullptr),
continueRun(true),
maxTotal(1)
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
,
mutex(QMutex::Recursive)
#endif
{
cutterDiff.reset(new CutterDiff());
}

BinDiff::~BinDiff()
{
rz_analysis_match_result_free(result);
}

bool BinDiff::hasData()
{
return result != nullptr;
}

void BinDiff::setFileA(QString filePath)
{
mutex.lock();
fileA = std::move(filePath);
mutex.unlock();
}

void BinDiff::setFileB(QString filePath)
{
mutex.lock();
fileB = std::move(filePath);
mutex.unlock();
}

void BinDiff::setAnalysisLevel(int aLevel)
{
mutex.lock();
level = aLevel;
mutex.unlock();
}

void BinDiff::setCompareLogic(int cLogic)
{
mutex.lock();
compareLogic = cLogic;
mutex.unlock();
}

void BinDiff::run()
{
qRegisterMetaType<BinDiffStatusDescription>();

mutex.lock();
rz_analysis_match_result_free(result);
continueRun = true;
maxTotal = 1; // maxTotal must be at least 1.
mutex.unlock();
cutterDiff->initCores();
cutterDiff->syncConfig();
cutterDiff->openFiles(fileA, fileB);
cutterDiff->analyzeCores(level);
result = cutterDiff->matchFunctions(compareLogic, threadCallback, this);
sortFunctions();
mutex.lock();
const bool canComplete = continueRun;
mutex.unlock();
if (canComplete) {
emit complete();
}
}

void BinDiff::cancel()
{
mutex.lock();
continueRun = false;
mutex.unlock();
}

static void setFunctionDescription(FunctionDescription *desc, const RzAnalysisFunction *func)
{
desc->offset = func->addr;
desc->linearSize = rz_analysis_function_linear_size(const_cast<RzAnalysisFunction *>(func));
desc->nargs = rz_analysis_arg_count(const_cast<RzAnalysisFunction *>(func));
desc->nlocals = rz_analysis_var_local_count(const_cast<RzAnalysisFunction *>(func));
desc->nbbs = rz_pvector_len(func->bbs);
desc->calltype = func->cc ? QString::fromUtf8(func->cc) : QString();
desc->name = func->name ? QString::fromUtf8(func->name) : QString();
desc->edges = rz_analysis_function_count_edges(func, nullptr);
desc->stackframe = func->maxstack;
}

struct MatchEntry
{
const RzAnalysisFunction *fcnA;
const RzAnalysisFunction *fcnB;
double similarity;
};

void BinDiff::sortFunctions()
{
if (!result) {
return;
}

matchedList.clear();
removedSet.clear();
addedSet.clear();

QHash<const RzAnalysisFunction *, MatchEntry> bestMatches;
QSet<const RzAnalysisFunction *> discardedA;

const RzAnalysisMatchPair *pair = nullptr;
const RzListIter *it = nullptr;

CutterRzListForeach (result->matches, it, RzAnalysisMatchPair, pair) {

auto *fcnA = static_cast<const RzAnalysisFunction *>(pair->pair_a);
auto *fcnB = static_cast<const RzAnalysisFunction *>(pair->pair_b);

auto hashIt = bestMatches.find(fcnB);

if (hashIt == bestMatches.end()) {
bestMatches.insert(fcnB, { fcnA, fcnB, pair->similarity });
continue;
}

if (pair->similarity > hashIt->similarity) {

discardedA.insert(hashIt->fcnA);

discardedA.remove(fcnA);

hashIt->fcnA = fcnA;
hashIt->similarity = pair->similarity;
} else {

discardedA.insert(fcnA);
}
}

for (const auto &entry : std::as_const(bestMatches)) {

BinDiffMatchDescription desc;

setFunctionDescription(&desc.original, entry.fcnA);
setFunctionDescription(&desc.modified, entry.fcnB);

desc.similarity = entry.similarity;
desc.simtype = RZ_ANALYSIS_SIMILARITY_TYPE_STR(entry.similarity);

matchedList.push_back(std::move(desc));
}
removedSet = std::move(discardedA);

const RzAnalysisFunction *func = nullptr;

CutterRzListForeach (result->unmatch_a, it, RzAnalysisFunction, func) {
removedSet.insert(func);
}
CutterRzListForeach (result->unmatch_b, it, RzAnalysisFunction, func) {
addedSet.insert(func);
}
}

QList<BinDiffMatchDescription> BinDiff::matches()
{
return matchedList;
}

QList<FunctionDescription> BinDiff::mismatch(bool originalFile)
{
QList<FunctionDescription> list;
for (const RzAnalysisFunction *func : (originalFile ? removedSet : addedSet)) {
FunctionDescription desc;
setFunctionDescription(&desc, func);
list.push_back(desc);
}
return list;
}

bool BinDiff::updateProgress(size_t nLeft, size_t nMatch)
{
mutex.lock();
if (nMatch > maxTotal) {
maxTotal = nMatch;
}
if (nLeft > maxTotal) {
maxTotal = nLeft;
}

BinDiffStatusDescription status;
status.total = maxTotal;
status.nLeft = nLeft;
status.nMatch = nMatch;

emit progress(status);
const bool ret = continueRun;
mutex.unlock();
return ret;
}
69 changes: 69 additions & 0 deletions src/core/BinDiff.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#ifndef CUTTER_BINDIFF_CORE_H
#define CUTTER_BINDIFF_CORE_H

#include "Cutter.h"
#include "CutterDescriptions.h"
#include "CutterDiff.h"

#include <QMutex>
#include <QThread>

#include <rz_analysis.h>

class CutterDiffWindow;
/**
* @brief The BinDiff class
* Thread run for processing the functional diffing and other large diffing processes.
* TODO: Move to common or tools/bindiff?
*/
class BinDiff : public QThread
{
Q_OBJECT
friend class CutterDiffWindow;

public:
explicit BinDiff();
virtual ~BinDiff();

void run();

void setFileA(QString filePth);
void setFileB(QString filePth);
void setAnalysisLevel(int aLevel);
void setCompareLogic(int cLogic);
bool hasData();

QList<BinDiffMatchDescription> matches();
QList<FunctionDescription> mismatch(bool originalFile);

public slots:
void cancel();

signals:
void progress(BinDiffStatusDescription status);
void complete();

private:
std::unique_ptr<CutterDiff> cutterDiff;
RzAnalysisMatchResult *result;
void sortFunctions();
QList<BinDiffMatchDescription> matchedList;
QSet<const RzAnalysisFunction *> removedSet;
QSet<const RzAnalysisFunction *> addedSet;
bool continueRun;
size_t maxTotal;
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
QMutex mutex;
#else
QRecursiveMutex mutex;
#endif
QString fileA;
QString fileB;
int level;
int compareLogic;

bool updateProgress(const size_t nLeft, const size_t nMatch);
static bool threadCallback(const size_t nLeft, const size_t nMatch, void *user);
};

#endif // CUTTER_BINDIFF_CORE_H
2 changes: 1 addition & 1 deletion src/core/Cutter.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ class CUTTER_EXPORT CutterCore : public QObject
/**
* @brief a wrapper around cmdRaw(const char *cmd,).
*/
QString cmdRaw(const QString &cmd) { return cmdRaw(cmd.toUtf8().constData()); };
QString cmdRaw(const QString &cmd) { return cmdRaw(cmd.toUtf8().constData()); }

/**
* @brief Execute a Rizin command \a cmd at \a address. The function will preform a silent seek
Expand Down
Loading
Loading