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/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/" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d56b8546..757189d2 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 @@ -61,7 +68,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 @@ -69,32 +76,34 @@ 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 - - uses: actions/setup-python@v1 + 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 }} - name: homebrew dependencies 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 - 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: | @@ -123,8 +132,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 @@ -138,8 +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_CRASH_REPORTS=ON \ + -DCUTTER_ENABLE_GRAPHVIZ=ON \ -DCUTTER_USE_BUNDLED_RIZIN=ON \ -DCUTTER_APPIMAGE_BUILD=ON \ -DCUTTER_ENABLE_PACKAGING=ON \ @@ -152,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 \ @@ -194,7 +208,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 @@ -205,7 +218,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 \ @@ -216,7 +228,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; @@ -240,7 +251,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 @@ -258,7 +268,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 ^ @@ -276,7 +285,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 }} @@ -284,7 +293,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 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 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 e7f950f2..aeb35127 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) @@ -26,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) @@ -39,9 +38,33 @@ 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) + +# Put Qt files in a separate folder +set_property(GLOBAL PROPERTY AUTOGEN_SOURCE_GROUP "Generated Files") set(CMAKE_CXX_STANDARD 11) @@ -130,7 +153,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/README.md b/README.md index 5642875b..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) @@ -23,9 +22,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 diff --git a/cmake/BundledRizin.cmake b/cmake/BundledRizin.cmake index 7955c279..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") @@ -56,14 +57,14 @@ 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 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/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 5e1c18a7..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) @@ -168,9 +164,9 @@ if(CUTTER_PACKAGE_RZ_GHIDRA) # installed Cutter. ExternalProject_Add(rz-ghidra GIT_REPOSITORY https://github.com/rizinorg/rz-ghidra - GIT_TAG v0.4.0 + #GIT_TAG v0.3.0 #GIT_TAG c7a50a2e7c0a95cd52b167c9ee0fa1805223f08e - #GIT_TAG dev + GIT_TAG dev #GIT_SHALLOW ON # disable this line when using commit hash CONFIGURE_COMMAND "" BUILD_COMMAND "" 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/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/docs/source/building.rst b/docs/source/building.rst index 5f2d9c0a..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: :: @@ -113,8 +116,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 +254,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 +272,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 a3e50eb2..00000000 Binary files a/docs/source/images/crash-dialog.png and /dev/null differ diff --git a/docs/source/images/success-dump-dialog.png b/docs/source/images/success-dump-dialog.png deleted file mode 100644 index 94144c85..00000000 Binary files a/docs/source/images/success-dump-dialog.png and /dev/null differ diff --git a/rizin b/rizin index 9023f8b9..12898a36 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit 9023f8b997db210cef3b9a25cf1748fbc94942ed +Subproject commit 12898a365e70c22892d78abdd2627e7269533c5f 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" diff --git a/scripts/breakpad_client.gyp b/scripts/breakpad_client.gyp deleted file mode 100644 index de079c8b..00000000 --- a/scripts/breakpad_client.gyp +++ /dev/null @@ -1,34 +0,0 @@ -{ - 'includes': [ - '../../build/common.gypi' - ], - 'targets': [ - { - 'target_name': 'build_all', - 'type': 'none', - 'dependencies': [ - './crash_generation/crash_generation.gyp:*', - './handler/exception_handler.gyp:*', - './sender/crash_report_sender.gyp:*', - ] - }, - { - 'target_name': 'common', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)', - ], - 'direct_dependent_settings': { - 'include_dirs': [ - '<(DEPTH)', - ] - }, - 'sources': [ - '<(DEPTH)/common/windows/guid_string.cc', - '<(DEPTH)/common/windows/guid_string.h', - '<(DEPTH)/common/windows/http_upload.h', - '<(DEPTH)/common/windows/string_utils.cc', - ] - } - ] -} diff --git a/scripts/breakpad_extract_symbols_appimage.py b/scripts/breakpad_extract_symbols_appimage.py deleted file mode 100644 index edf2f7d3..00000000 --- a/scripts/breakpad_extract_symbols_appimage.py +++ /dev/null @@ -1,55 +0,0 @@ - -import sys -import os -import subprocess -import re -import atexit - -if len(sys.argv) != 3: - print(f"usage: {sys.argv[0]} [Cutter.AppImage] [symbols dir]") - exit(1) - -root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) - -def store_syms(syms, syms_dir): - m = re.match(b"MODULE ([^ ]+) ([^ ]+) ([^ ]+) (.+)\n.*", syms) - if m is None: - print("Invalid dump_syms output") - return - - (modos, modarch, modhash, modname) = m.groups() - modname = modname.decode("utf-8") - modhash = modhash.decode("utf-8") - symdir = os.path.join(syms_dir, modname, modhash) - symfile = f"{modname}.sym" - os.makedirs(symdir) - symfile_path = os.path.join(symdir, symfile) - with open(symfile_path, "wb") as f: - f.write(syms) - - print(symfile_path) - -def dump_syms(binary, syms_dir): - dump_syms_exec = os.path.join(root, "breakpad/src/tools/linux/dump_syms/dump_syms") - syms = subprocess.run([dump_syms_exec, binary], capture_output=True).stdout - store_syms(syms, syms_dir) - -appimage = sys.argv[1] -syms_dst = sys.argv[2] - -# stdbuf workaround is needed before https://github.com/AppImage/AppImageKit/commit/e827baa719f5444aeef7202fe1f71c97d4200dde -appimage_p = subprocess.Popen(["stdbuf", "-oL", appimage, "--appimage-mount"], stdout=subprocess.PIPE) -def kill_appimage(): - appimage_p.kill() -atexit.register(kill_appimage) -mount_dir = appimage_p.stdout.readline().strip().decode("utf-8") - -binaries = [ os.path.join(mount_dir, "usr/bin/cutter") ] -for f in os.scandir(os.path.join(mount_dir, "usr/lib")): - if f.is_dir() or f.is_symlink(): - continue - binaries.append(f.path) - -for b in binaries: - dump_syms(b, syms_dst) - diff --git a/scripts/breakpad_macos.patch b/scripts/breakpad_macos.patch deleted file mode 100644 index b446e4f5..00000000 --- a/scripts/breakpad_macos.patch +++ /dev/null @@ -1,1377 +0,0 @@ -From e2797a7f1853a22a7c7b57dcb3e642f9325a5a88 Mon Sep 17 00:00:00 2001 -From: Ville Suoranta -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 47357c69..f340ee6e 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 @@ -72,7 +73,6 @@ set(SOURCES widgets/CutterTreeWidget.cpp widgets/GraphWidget.cpp widgets/OverviewWidget.cpp - common/JsonTreeItem.cpp common/JsonModel.cpp dialogs/VersionInfoDialog.cpp widgets/FlirtWidget.cpp @@ -155,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 @@ -222,7 +223,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 @@ -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") @@ -442,8 +437,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 +450,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 @@ -465,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 8cb4347f..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 @@ -74,6 +62,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. diff --git a/src/common/BugReporting.cpp b/src/common/BugReporting.cpp index 1f1ca3ea..95da855f 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/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/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 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/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/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/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/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 490e363b..dad33c8e 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)) { @@ -377,6 +370,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) { @@ -406,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()) { @@ -468,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; @@ -486,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(); @@ -510,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); @@ -530,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) { @@ -687,7 +656,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 { @@ -746,34 +715,31 @@ 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)); + + 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; + 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) @@ -790,14 +756,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); } @@ -879,8 +851,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 = (char *)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) @@ -964,12 +936,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); } @@ -1055,24 +1026,20 @@ RVA CutterCore::prevOpAddr(RVA startAddr, int count) RVA CutterCore::nextOpAddr(RVA startAddr, int count) { CORE_LOCK(); + auto seek = seekTemp(startAddr); + auto vec = + fromOwned(rz_core_analysis_bytes(core, core->block, (int)core->blocksize, count + 1)); - CutterJson array = - Core()->cmdj("pdj " + QString::number(count + 1) + " @ " + QString::number(startAddr)); - if (!array.size()) { - return startAddr + 1; + 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.get())); + if (!(ab && ab->op)) { + return addr; } - - RVA offset = instValue[RJsonKey::offset].toRVA(); - if (offset == RVA_INVALID) { - return startAddr + 1; - } - - return offset; + addr = ab->op->addr; + return addr; } RVA CutterCore::getOffset() @@ -1296,7 +1263,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) @@ -1370,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, ""); @@ -1400,19 +1358,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() @@ -1440,17 +1387,7 @@ bool CutterCore::registerDecompiler(Decompiler *decompiler) return true; } -CutterJson CutterCore::getFileInfo() -{ - return cmdj("ij"); -} - -CutterJson CutterCore::getFileVersionInfo() -{ - return cmdj("iVj"); -} - -QString CutterCore::getSignatureInfo() +CutterJson CutterCore::getSignatureInfo() { CORE_LOCK(); RzBinFile *cur = rz_bin_cur(core->bin); @@ -1462,7 +1399,17 @@ QString CutterCore::getSignatureInfo() if (!signature) { return {}; } - return fromOwnedCharPtr(signature); + return parseJson(signature, nullptr); +} + +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 @@ -1846,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; } @@ -2139,9 +2075,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(); @@ -2195,7 +2137,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); @@ -2919,11 +2862,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) { @@ -3099,12 +3040,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_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(); @@ -3179,21 +3116,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; } @@ -3349,22 +3303,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 = rz_str_enc_as_string(str->type); + string.size = str->size; + string.length = str->length; + string.section = section ? section->name : ""; ret << string; } @@ -3881,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; } @@ -3923,8 +3892,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; } @@ -3979,22 +3947,34 @@ 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; + for (const auto &v : CutterPVector(&fcn->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; @@ -4138,18 +4118,33 @@ 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(); + auto vec = fromOwned( + rz_pvector_new(reinterpret_cast(rz_analysis_disasm_text_free))); + if (!vec) { + return {}; } + RzCoreDisasmOptions options = {}; + options.cbytes = 1; + 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.get())) { + DisassemblyLine line; + line.offset = t->offset; + line.text = ansiEscapeToHtml(t->text); + line.arrow = t->arrow; + r << line; + } return r; } @@ -4161,28 +4156,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) @@ -4212,10 +4215,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() @@ -4238,7 +4240,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 }, @@ -4252,7 +4254,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(); @@ -4316,7 +4319,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); @@ -4491,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); } @@ -4501,19 +4503,80 @@ 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; } + +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 b5be0ff8..5138807d 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -17,6 +17,7 @@ #include #include #include +#include class AsyncTaskManager; class BasicInstructionHighlighter; @@ -31,6 +32,7 @@ class RizinTaskDialog; #include "common/Helpers.h" #include +#include #define Core() (CutterCore::instance()) @@ -59,6 +61,8 @@ struct CUTTER_EXPORT RegisterRef QString name; }; +using PRzAnalysisBytes = std::unique_ptr; + class CUTTER_EXPORT CutterCore : public QObject { Q_OBJECT @@ -81,6 +85,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 @@ -90,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 @@ -112,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 @@ -148,56 +142,41 @@ public: return cmdRawAt(str.toUtf8().constData(), address); } - void applyAtSeek(std::function fn, RVA address) + class SeekReturn { - RVA oldOffset = getOffset(); - seekSilent(address); - fn(); - seekSilent(oldOffset); - } + RVA returnAddress; + bool empty = true; - void *returnAtSeek(std::function fn, RVA address) + 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; + } + }; + }; + + SeekReturn seekTemp(RVA address) { - RVA oldOffset = getOffset(); + SeekReturn returner(getOffset()); seekSilent(address); - void *ret = fn(); - seekSilent(oldOffset); - return ret; + return returner; } 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); - /** - * @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(); + + QString getRizinVersionReadable(const char *program = nullptr); QString getVersionInformation(); CutterJson parseJson(char *res, const char *cmd = nullptr); @@ -257,6 +236,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); @@ -569,14 +549,12 @@ public: bool registerDecompiler(Decompiler *decompiler); RVA getOffsetJump(RVA addr); - CutterJson getFileInfo(); - QString getSignatureInfo(); - CutterJson getFileVersionInfo(); + CutterJson getSignatureInfo(); + bool existsFileInfo(); void setGraphEmpty(bool empty); bool isGraphEmpty(); - void getOpcodes(); - QList opcodes; + void getRegs(); QList regs; void setSettings(); @@ -688,8 +666,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 */ @@ -744,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/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/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; }; 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/core/MainWindow.cpp b/src/core/MainWindow.cpp index e599809e..e873b0d7 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); @@ -637,7 +637,7 @@ bool MainWindow::openProject(const QString &file) void MainWindow::finalizeOpen() { - core->getOpcodes(); + core->getRegs(); core->updateSeek(); refreshAll(); // Add fortune message @@ -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); @@ -1108,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); @@ -1692,35 +1698,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 +1762,25 @@ 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()]; + 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; + } - // Use cmd because cmdRaw would not handle such input - fileOut << Core()->cmd(cmd + " $s @ 0"); + + + 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)); + fileOut << string.get(); } void MainWindow::on_actionApplySigFromFile_triggered() 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..e39f964b --- /dev/null +++ b/src/core/RizinCpp.h @@ -0,0 +1,168 @@ +/** \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: + 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; + + 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: + 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; + + 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/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 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/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/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/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/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. diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index f3f2dcfb..ab54328d 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -313,34 +313,33 @@ 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 = fromOwned( + 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 +481,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 +507,6 @@ void DisassemblyContextMenu::aboutToShowSlot() } } } - rz_pvector_free(vec); if (memBaseReg.isEmpty()) { // hide structure offset menu @@ -532,7 +524,7 @@ void DisassemblyContextMenu::aboutToShowSlot() continue; } structureOffsetMenu->addAction("[" + memBaseReg + " + " + ty->path + "]") - ->setData(ty->path); + ->setData(QString(ty->path)); } rz_list_free(typeoffs); } @@ -726,18 +718,13 @@ void DisassemblyContextMenu::on_actionNopInstruction_triggered() void DisassemblyContextMenu::showReverseJmpQuery() { - QString type; - - CutterJson array = Core()->cmdj("pdj 1 @ " + RzAddressString(offset)); - if (!array.size()) { + actionJmpReverse.setVisible(false); + auto ab = Core()->getRzAnalysisBytesSingle(offset); + if (!(ab && ab->op)) { return; } - - type = array.first()["type"].toString(); - if (type == "cjmp") { + if (ab->op->type == RZ_ANALYSIS_OP_TYPE_CJMP) { actionJmpReverse.setVisible(true); - } else { - actionJmpReverse.setVisible(false); } } @@ -1036,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(); 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) { 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); } 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 diff --git a/src/translations b/src/translations index 97429865..41c0c778 160000 --- a/src/translations +++ b/src/translations @@ -1 +1 @@ -Subproject commit 974298653ba71b958e1b6c83f6011f5fefff6236 +Subproject commit 41c0c778b942577749ea2fed117e48a2cf3892df diff --git a/src/widgets/CallGraph.cpp b/src/widgets/CallGraph.cpp index c66a862e..9836a561 100644 --- a/src/widgets/CallGraph.cpp +++ b/src/widgets/CallGraph.cpp @@ -7,9 +7,11 @@ #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 +25,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)) { @@ -37,6 +44,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() @@ -47,17 +55,14 @@ 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) { 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 +77,70 @@ 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); +} + 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 = 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; + 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..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" @@ -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; @@ -31,7 +30,7 @@ private: RVA lastLoadedAddress = RVA_INVALID; }; -class CallGraphWidget : public AddressableDockWidget +class CallGraphWidget : public MemoryDockWidget { Q_OBJECT @@ -39,6 +38,8 @@ public: explicit CallGraphWidget(MainWindow *main, bool global); ~CallGraphWidget(); + QString getWidgetType() const; + protected: QString getWindowTitle() const override; 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/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/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/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 66265d43..1df65b32 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" @@ -32,24 +31,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,82 +113,68 @@ 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; - // 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; - } - } + 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) + "%"); + 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().isEmpty()) { + if (!Core()->getSignatureInfo().size()) { ui->certificateButton->setEnabled(false); } - if (!Core()->getFileVersionInfo().size()) { - ui->versioninfoButton->setEnabled(false); - } + ui->versioninfoButton->setEnabled(Core()->existsFileInfo()); } 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() 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 + + diff --git a/src/widgets/DisassemblerGraphView.cpp b/src/widgets/DisassemblerGraphView.cpp index 09dacdd2..69f87e5b 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,15 +162,15 @@ void DisassemblerGraphView::connectSeekChanged(bool disconn) DisassemblerGraphView::~DisassemblerGraphView() { - for (QShortcut *shortcut : shortcuts) { - delete shortcut; - } + qDeleteAll(shortcuts); + shortcuts.clear(); } void DisassemblerGraphView::refreshView() { CutterGraphView::refreshView(); loadCurrentGraph(); + breakpoints = Core()->getBreakpointsAddresses(); emit viewRefreshed(); } @@ -182,13 +182,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 +190,19 @@ 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)) { + currentFcnAddr = fcn->addr; + auto fcnName = fromOwned(rz_str_escape_utf8_for_json(fcn->name, -1)); + 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,67 @@ 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; + auto vec = fromOwned( + rz_pvector_new(reinterpret_cast(rz_analysis_disasm_text_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 +307,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() @@ -365,8 +372,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; @@ -887,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; @@ -897,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, 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/FunctionsWidget.cpp b/src/widgets/FunctionsWidget.cpp index 6324467b..61dad3fe 100644 --- a/src/widgets/FunctionsWidget.cpp +++ b/src/widgets/FunctionsWidget.cpp @@ -231,7 +231,15 @@ 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 = fromOwnedCharPtr( + rz_core_print_disasm_strings(Core()->core(), RZ_CORE_DISASM_STRINGS_MODE_FUNCTION, + 0, NULL)); + summary = strings.split('\n', CUTTER_QT_SKIP_EMPTY_PARTS); + } + const QFont &fnt = Config()->getFont(); QFontMetrics fm { fnt }; @@ -245,7 +253,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 {}; } } @@ -564,7 +572,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/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; 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 diff --git a/src/widgets/HexdumpWidget.cpp b/src/widgets/HexdumpWidget.cpp index ce12be37..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"); @@ -240,28 +239,28 @@ 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 - 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); 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: 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 { 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 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: diff --git a/src/widgets/SimpleTextGraphView.cpp b/src/widgets/SimpleTextGraphView.cpp index ad4c5deb..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 - auto x = block.x + padding; - int y = block.y + padding + p.fontMetrics().ascent(); + QFontMetrics fm = QFontMetrics(p.font()); + auto x = block.x + padding / 2; + int y = block.y + padding / 2 + fm.ascent(); p.drawText(QPoint(x, y), content.text); } diff --git a/src/widgets/TypesWidget.cpp b/src/widgets/TypesWidget.cpp index 8e7a0e6e..3891314e 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(); } @@ -289,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(); } 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;