diff --git a/.github/workflows/Nuget-publishing.yml b/.github/workflows/Nuget-publishing.yml index 25aff0c54a..0db0ed2232 100644 --- a/.github/workflows/Nuget-publishing.yml +++ b/.github/workflows/Nuget-publishing.yml @@ -31,9 +31,6 @@ on: permissions: packages: write -env: - CTEST_PARALLEL_LEVEL: 40 - jobs: Windows: runs-on: ${{ matrix.config.os }} @@ -67,6 +64,11 @@ jobs: compiler: [ gcc ] steps: - uses: actions/checkout@v4 + - name: 'Determine test parallelism' + shell: pwsh + run: | + "CTEST_PARALLEL_LEVEL=$([Environment]::ProcessorCount)" | + Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: '🛠️ Win MSVC 64 setup' if: contains(matrix.config.name, 'MSVC 64') @@ -96,7 +98,7 @@ jobs: -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} cmake --build . --config ${{ matrix.config.build_type }} cmake --install . --strip --config ${{ matrix.config.build_type }} - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} -VV -C ${{ matrix.config.build_type }} + ctest --parallel "$CTEST_PARALLEL_LEVEL" -VV -C ${{ matrix.config.build_type }} # vctip.exe (MSVC telemetry) and antivirus scans may transiently hold # handles inside the build directory and make the rename fail, so # retry for a while, killing vctip on each attempt @@ -133,7 +135,7 @@ jobs: -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} cmake --build . --config ${{ matrix.config.build_type }} cmake --install . --strip --config ${{ matrix.config.build_type }} - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} -VV -C ${{ matrix.config.build_type }} + ctest --parallel "$CTEST_PARALLEL_LEVEL" -VV -C ${{ matrix.config.build_type }} # vctip.exe (MSVC telemetry) and antivirus scans may transiently hold # handles inside the build directory and make the rename fail, so # retry for a while, killing vctip on each attempt @@ -180,6 +182,9 @@ jobs: compiler: [ gcc ] steps: - uses: actions/checkout@v4 + - name: 'Determine test parallelism' + shell: bash + run: echo "CTEST_PARALLEL_LEVEL=$(sysctl -n hw.logicalcpu)" >> "$GITHUB_ENV" - name: '🚧 Mac build' if: contains(matrix.config.name, 'macos-x64') @@ -198,7 +203,7 @@ jobs: -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} cmake --build . --config ${{ matrix.config.build_type }} cmake --install . --strip - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} -VV -C ${{ matrix.config.build_type }} + ctest --parallel "$CTEST_PARALLEL_LEVEL" -VV -C ${{ matrix.config.build_type }} - name: '📦 Pack artifact' if: always() @@ -265,6 +270,9 @@ jobs: compiler: [ gcc ] steps: - uses: actions/checkout@v4 + - name: 'Determine test parallelism' + shell: bash + run: echo "CTEST_PARALLEL_LEVEL=$(getconf _NPROCESSORS_ONLN)" >> "$GITHUB_ENV" - name: '🚧 Linux x64/x86 build' if: contains(matrix.config.arch, 'x64') || contains(matrix.config.arch, 'x86') @@ -291,7 +299,7 @@ jobs: -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} cmake --build . --config ${{ matrix.config.build_type }} cmake --install . --strip - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} -VV -C ${{ matrix.config.build_type }} + ctest --parallel "$CTEST_PARALLEL_LEVEL" -VV -C ${{ matrix.config.build_type }} - name: '🚧 Linux aarch64 build' if: contains(matrix.config.arch, 'aarch64') @@ -309,7 +317,7 @@ jobs: -DCMAKE_INSTALL_PREFIX:PATH=instdir cmake --build . --config ${{ matrix.config.build_type }} cmake --install . --strip - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} -VV -C ${{ matrix.config.build_type }} + ctest --parallel "$CTEST_PARALLEL_LEVEL" -VV -C ${{ matrix.config.build_type }} - name: '🚧 Linux ppc64le build' if: contains(matrix.config.arch, 'ppc64le') @@ -335,7 +343,7 @@ jobs: -DCMAKE_INSTALL_PREFIX:PATH=/instdir cmake --build . --config ${{ matrix.config.build_type }} cmake --install . --strip - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} -VV -C ${{ matrix.config.build_type }} + ctest --parallel "$(getconf _NPROCESSORS_ONLN)" -VV -C ${{ matrix.config.build_type }} - name: '📦 Pack artifact' if: always() diff --git a/.github/workflows/build-uc2.yml b/.github/workflows/build-uc2.yml index 12d69d9c56..7295d3e68b 100644 --- a/.github/workflows/build-uc2.yml +++ b/.github/workflows/build-uc2.yml @@ -31,7 +31,6 @@ on: env: # Specify build type either according to the tag release or manual override BUILD_TYPE: ${{ inputs.buildType != '' && inputs.buildType || startsWith(github.ref, 'refs/tags') && 'Release' || 'Debug' }} - CTEST_PARALLEL_LEVEL: 40 jobs: Windows: @@ -129,6 +128,12 @@ jobs: steps: - uses: actions/checkout@v4 + - name: 'Determine test parallelism' + shell: pwsh + run: | + "CTEST_PARALLEL_LEVEL=$([Environment]::ProcessorCount)" | + Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + - name: '🛠️ Win MINGW setup' if: contains(matrix.config.mingw, 'MINGW') uses: msys2/setup-msys2@v2 @@ -168,7 +173,7 @@ jobs: -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} cmake --build . --config ${{ env.BUILD_TYPE }} cmake --install . --strip --config ${{ env.BUILD_TYPE }} - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} --output-on-failure -C ${{ env.BUILD_TYPE }} + ctest --parallel "$CTEST_PARALLEL_LEVEL" --output-on-failure -C ${{ env.BUILD_TYPE }} # vctip.exe (MSVC telemetry) and antivirus scans may transiently hold # handles inside the build directory and make the rename fail, so # retry for a while, killing vctip on each attempt @@ -204,7 +209,7 @@ jobs: -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} cmake --build . --config ${{ env.BUILD_TYPE }} cmake --install . --strip --config ${{ env.BUILD_TYPE }} - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} --output-on-failure -C ${{ env.BUILD_TYPE }} + ctest --parallel "$CTEST_PARALLEL_LEVEL" --output-on-failure -C ${{ env.BUILD_TYPE }} # vctip.exe (MSVC telemetry) and antivirus scans may transiently hold # handles inside the build directory and make the rename fail, so # retry for a while, killing vctip on each attempt @@ -242,7 +247,7 @@ jobs: -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} cmake --build . --config ${{ env.BUILD_TYPE }} cmake --install . --strip - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} --output-on-failure -C ${{ env.BUILD_TYPE }} + ctest --parallel "$CTEST_PARALLEL_LEVEL" --output-on-failure -C ${{ env.BUILD_TYPE }} - name: '📦 Pack artifact' if: always() @@ -254,10 +259,22 @@ jobs: - name: '📤 Upload artifact' if: always() + id: upload_artifact + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + path: ./${{ matrix.config.artifact }} + name: ${{ matrix.config.artifact }} + compression-level: 0 + + - name: '📤 Retry artifact upload' + if: steps.upload_artifact.outcome == 'failure' uses: actions/upload-artifact@v4 with: path: ./${{ matrix.config.artifact }} name: ${{ matrix.config.artifact }} + compression-level: 0 + overwrite: true Macos: runs-on: ${{ matrix.config.os }} @@ -319,6 +336,9 @@ jobs: compiler: [ gcc ] steps: - uses: actions/checkout@v4 + - name: 'Determine test parallelism' + shell: bash + run: echo "CTEST_PARALLEL_LEVEL=$(sysctl -n hw.logicalcpu)" >> "$GITHUB_ENV" - name: '🚧 Mac build' if: contains(matrix.config.name, 'macos') shell: bash @@ -338,7 +358,7 @@ jobs: -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} cmake --build . --config ${{ env.BUILD_TYPE }} cmake --install . --strip - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} --output-on-failure -C ${{ env.BUILD_TYPE }} + ctest --parallel "$CTEST_PARALLEL_LEVEL" --output-on-failure -C ${{ env.BUILD_TYPE }} - name: '🚧 Android x86_64 build' if: contains(matrix.config.name, 'android') @@ -406,10 +426,22 @@ jobs: - name: '📤 Upload artifact' if: always() + id: upload_artifact + continue-on-error: true uses: actions/upload-artifact@v4 with: path: ./${{ matrix.config.artifact }} name: ${{ matrix.config.artifact }} + compression-level: 0 + + - name: '📤 Retry artifact upload' + if: steps.upload_artifact.outcome == 'failure' + uses: actions/upload-artifact@v4 + with: + path: ./${{ matrix.config.artifact }} + name: ${{ matrix.config.artifact }} + compression-level: 0 + overwrite: true Linux: runs-on: ${{ matrix.config.os }} @@ -475,6 +507,9 @@ jobs: compiler: [ gcc ] steps: - uses: actions/checkout@v4 + - name: 'Determine test parallelism' + shell: bash + run: echo "CTEST_PARALLEL_LEVEL=$(getconf _NPROCESSORS_ONLN)" >> "$GITHUB_ENV" - name: '🚧 Linux x64/x86 build' if: contains(matrix.config.arch, 'x64') || contains(matrix.config.arch, 'x86') shell: bash @@ -500,7 +535,7 @@ jobs: -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} cmake --build . --config ${{ env.BUILD_TYPE }} cmake --install . --strip - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} --output-on-failure -C ${{ env.BUILD_TYPE }} + ctest --parallel "$CTEST_PARALLEL_LEVEL" --output-on-failure -C ${{ env.BUILD_TYPE }} - name: '🚧 Linux aarch64 build' if: contains(matrix.config.arch, 'aarch64') @@ -518,7 +553,7 @@ jobs: -DCMAKE_INSTALL_PREFIX:PATH=instdir cmake --build . --config ${{ env.BUILD_TYPE }} cmake --install . --strip - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} --output-on-failure -C ${{ env.BUILD_TYPE }} + ctest --parallel "$CTEST_PARALLEL_LEVEL" --output-on-failure -C ${{ env.BUILD_TYPE }} - name: '🚧 Linux ppc64le build' if: contains(matrix.config.arch, 'ppc64le') @@ -544,7 +579,7 @@ jobs: -DCMAKE_INSTALL_PREFIX:PATH=/instdir cmake --build . --config ${{ env.BUILD_TYPE }} cmake --install . --strip - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} --output-on-failure -C ${{ env.BUILD_TYPE }} + ctest --parallel "$(getconf _NPROCESSORS_ONLN)" --output-on-failure -C ${{ env.BUILD_TYPE }} - name: '📦 Pack artifact' if: always() @@ -556,10 +591,149 @@ jobs: - name: '📤 Upload artifact' if: always() + id: upload_artifact + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + path: ./${{ matrix.config.artifact }} + name: ${{ matrix.config.artifact }} + compression-level: 0 + + - name: '📤 Retry artifact upload' + if: steps.upload_artifact.outcome == 'failure' uses: actions/upload-artifact@v4 with: path: ./${{ matrix.config.artifact }} name: ${{ matrix.config.artifact }} + compression-level: 0 + overwrite: true + + PythonRegressions: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + name: 'python regressions' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: 'Build current Python binding library' + shell: bash + run: | + workers=$(getconf _NPROCESSORS_ONLN) + python_executable=$(python -c 'import sys; print(sys.executable)') + python -m pip install --disable-pip-version-check capstone==6.0.0a2 + cmake \ + -S . \ + -B build-python-regressions \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=ON \ + -DPYTHON_EXECUTABLE="$python_executable" \ + -DUNICORN_TEST_PYTHON_REGRESSIONS=ON + cmake --build build-python-regressions \ + --target unicorn --parallel "$workers" + ctest \ + --test-dir build-python-regressions \ + --label-regex python-regression \ + --parallel "$workers" \ + --no-tests=error \ + --output-on-failure + + FuzzSanitizers: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + name: 'clang asan ubsan libfuzzer' + env: + ASAN_OPTIONS: detect_leaks=1:abort_on_error=1 + UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 + steps: + - uses: actions/checkout@v4 + - name: 'Build fuzz targets' + shell: bash + run: | + workers=$(getconf _NPROCESSORS_ONLN) + sanitizer_flags='-fsanitize=address,undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer' + cmake \ + -S . \ + -B build-fuzz \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_C_FLAGS="$sanitizer_flags" \ + -DCMAKE_EXE_LINKER_FLAGS='-fsanitize=address,undefined' \ + -DCMAKE_SHARED_LINKER_FLAGS='-fsanitize=address,undefined' \ + -DUNICORN_BUILD_TESTS=OFF \ + -DUNICORN_FUZZ=ON \ + -DUNICORN_FUZZ_LIBFUZZER=ON \ + -DUNICORN_INSTALL=OFF + cmake --build build-fuzz --parallel "$workers" + - name: 'Run nested timeout sanitizer regression' + shell: bash + run: | + workers=$(getconf _NPROCESSORS_ONLN) + sanitizer_flags='-fsanitize=address,undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer' + cmake \ + -S . \ + -B build-sanitizer-tests \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_C_FLAGS="$sanitizer_flags" \ + -DCMAKE_EXE_LINKER_FLAGS='-fsanitize=address,undefined' \ + -DCMAKE_SHARED_LINKER_FLAGS='-fsanitize=address,undefined' \ + -DUNICORN_ARCH=x86 \ + -DUNICORN_BUILD_TESTS=ON \ + -DUNICORN_FUZZ=OFF \ + -DUNICORN_INSTALL=OFF + cmake --build build-sanitizer-tests \ + --target test_ctl --parallel "$workers" + ctest \ + --test-dir build-sanitizer-tests \ + --show-only \ + --tests-regex '^unit[.]test_ctl[.]test_uc_nested_timeout$' + timeout --signal=TERM --kill-after=5s 60s ctest \ + --test-dir build-sanitizer-tests \ + --tests-regex '^unit[.]test_ctl[.]test_uc_nested_timeout$' \ + --no-tests=error \ + --output-on-failure + - name: 'Run fuzz' + shell: bash + run: | + cp -R tests/fuzz/corpus/fuzz_uc_api build-fuzz/corpus-api + mkdir -p build-fuzz/artifacts + for target_path in build-fuzz/fuzz_emu_*; do + if [ ! -x "$target_path" ]; then + continue + fi + target=$(basename "$target_path") + artifact_dir="build-fuzz/artifacts/$target/" + corpus_dir=$(mktemp -d "build-fuzz/corpus-$target.XXXXXX") + cp -R tests/fuzz/corpus/raw/. "$corpus_dir" + mkdir -p "$artifact_dir" + "$target_path" \ + -runs=64 \ + -timeout=5 \ + -max_len=4096 \ + -artifact_prefix="$artifact_dir" \ + -dict=tests/fuzz/corpus/fuzz_emu.dict \ + "$corpus_dir" + done + mkdir -p build-fuzz/artifacts/fuzz_uc_api + build-fuzz/fuzz_uc_api \ + -runs=128 \ + -timeout=5 \ + -max_len=4096 \ + -artifact_prefix=build-fuzz/artifacts/fuzz_uc_api/ \ + -dict=tests/fuzz/corpus/fuzz_uc_api.dict \ + build-fuzz/corpus-api + - name: '📤 Upload fuzz artifacts' + if: failure() + uses: actions/upload-artifact@v4 + with: + name: fuzz-sanitizer-artifacts + path: build-fuzz/artifacts + if-no-files-found: ignore AlpineLinux: runs-on: ${{ matrix.config.os }} @@ -626,13 +800,49 @@ jobs: steps: - uses: actions/checkout@v4 + - name: 'Determine test parallelism' + shell: bash + run: echo "CTEST_PARALLEL_LEVEL=$(getconf _NPROCESSORS_ONLN)" >> "$GITHUB_ENV" + + - name: 'Bootstrap Alpine apk-tools' + shell: bash + env: + APK_TOOLS_PACKAGE_URL: https://dl-cdn.alpinelinux.org/alpine/v3.22/main/x86_64/apk-tools-static-2.14.10-r0.apk + APK_TOOLS_SHA256: f9c4c5675cdbf597a151aab6a9ba05983bfe5d927632c36c4575e7551ff55608 + run: | + set -euo pipefail + package="$RUNNER_TEMP/apk-tools-static.apk" + extract_dir="$(mktemp -d "$RUNNER_TEMP/apk-tools-static.XXXXXX")" + curl --fail --location --show-error --silent \ + --retry 5 --retry-delay 2 --retry-all-errors \ + --connect-timeout 30 --max-time 180 \ + --output "$package" "$APK_TOOLS_PACKAGE_URL" + tar -xzf "$package" -C "$extract_dir" sbin/apk.static + printf '%s %s\n' "$APK_TOOLS_SHA256" \ + "$extract_dir/sbin/apk.static" | sha256sum --check - + install -m 0755 "$extract_dir/sbin/apk.static" "$RUNNER_TEMP/apk" + nohup python3 -m http.server 38473 --bind 127.0.0.1 \ + --directory "$extract_dir/sbin" \ + >"$RUNNER_TEMP/apk-http.log" 2>&1 & + echo $! >"$RUNNER_TEMP/apk-http.pid" + - name: '🚧 Setup Alpine Linux' uses: jirutka/setup-alpine@v1 with: + apk-tools-url: 'http://127.0.0.1:38473/apk.static#!sha256!f9c4c5675cdbf597a151aab6a9ba05983bfe5d927632c36c4575e7551ff55608' branch: v3.22 arch: ${{ matrix.config.arch }} + mirror-url: https://dl-cdn.alpinelinux.org/alpine packages: cmake build-base automake cmocka-dev pkgconfig ${{ matrix.compiler }} ninja linux-headers + - name: 'Stop Alpine bootstrap server' + if: always() + shell: bash + run: | + if [[ -f "$RUNNER_TEMP/apk-http.pid" ]]; then + kill "$(cat "$RUNNER_TEMP/apk-http.pid")" || true + fi + - name: '🚧 Linux Alpine build' shell: alpine.sh --root {0} run: | @@ -647,7 +857,7 @@ jobs: -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} cmake --build . --config ${{ env.BUILD_TYPE }} cmake --install . --strip - ctest --parallel ${{ env.CTEST_PARALLEL_LEVEL }} --output-on-failure -C ${{ env.BUILD_TYPE }} + ctest --parallel "$CTEST_PARALLEL_LEVEL" --output-on-failure -C ${{ env.BUILD_TYPE }} - name: '📦 Pack artifact' if: always() @@ -659,7 +869,19 @@ jobs: - name: '📤 Upload artifact' if: always() + id: upload_artifact + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + path: ./${{ matrix.config.artifact }} + name: ${{ matrix.config.artifact }} + compression-level: 0 + + - name: '📤 Retry artifact upload' + if: steps.upload_artifact.outcome == 'failure' uses: actions/upload-artifact@v4 with: path: ./${{ matrix.config.artifact }} name: ${{ matrix.config.artifact }} + compression-level: 0 + overwrite: true diff --git a/.github/workflows/build-wheels-publish.yml b/.github/workflows/build-wheels-publish.yml index be9eb29179..b5df5047c7 100644 --- a/.github/workflows/build-wheels-publish.yml +++ b/.github/workflows/build-wheels-publish.yml @@ -73,13 +73,6 @@ jobs: name=$(echo -n "$name" | sed -e 's/[ \t:\/\\"<>|*?]/-/g' -e 's/--*/-/g' | sed -e 's/\-$//') echo "ARTIFACT_NAME=$name" >> $GITHUB_ENV - - name: "Increase pagefile to avoid OOM" - if: runner.os == 'Windows' - uses: al-cheb/configure-pagefile-action@v1.4 - with: - minimum-size: 8GB - disk-root: "C:" - - name: '🛠️ Add msbuild to PATH' if: runner.os == 'Windows' uses: microsoft/setup-msbuild@v2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 83732fb1d5..4f5baa582d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,9 +83,13 @@ include(cmake/bundle_static.cmake) option(BUILD_SHARED_LIBS "Build shared instead of static library" ${PROJECT_IS_TOP_LEVEL}) option(UNICORN_LEGACY_STATIC_ARCHIVE "Enable Unicorn v1 style all-in-one objects archive for libunicorn.a" ${PROJECT_IS_TOP_LEVEL}) option(UNICORN_FUZZ "Enable fuzzing" OFF) +option(UNICORN_FUZZ_LIBFUZZER "Build fuzz targets with libFuzzer" OFF) option(UNICORN_LOGGING "Enable logging" OFF) option(UNICORN_BUILD_TESTS "Build unicorn tests" ${PROJECT_IS_TOP_LEVEL}) +option(UNICORN_TEST_PYTHON_REGRESSIONS + "Run Python regressions against the current shared library" OFF) option(UNICORN_INSTALL "Enable unicorn installation" ${PROJECT_IS_TOP_LEVEL}) +option(UNICORN_ENABLE_LTO "Enable interprocedural optimization" OFF) set(UNICORN_ARCH "x86;arm;aarch64;riscv;mips;sparc;m68k;ppc;s390x;tricore" CACHE STRING "Enabled unicorn architectures") option(UNICORN_TRACER "Trace unicorn execution" OFF) # On Windows the code generation buffer is committed lazily through a @@ -94,6 +98,17 @@ option(UNICORN_TRACER "Trace unicorn execution" OFF) # non-Windows platforms. option(WIN32_ENABLE_VEH "Enable the Windows vectored exception handler for lazy code buffer commit" ON) +if(UNICORN_ENABLE_LTO) + include(CheckIPOSupported) + check_ipo_supported(RESULT UNICORN_LTO_SUPPORTED + OUTPUT UNICORN_LTO_ERROR) + if(NOT UNICORN_LTO_SUPPORTED) + message(FATAL_ERROR + "Interprocedural optimization is not supported: ${UNICORN_LTO_ERROR}") + endif() + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) +endif() + foreach(ARCH_LOOP ${UNICORN_ARCH}) string(TOUPPER "${ARCH_LOOP}" ARCH_LOOP) set(UNICORN_HAS_${ARCH_LOOP} TRUE) @@ -1435,6 +1450,11 @@ endif() if(MINGW) set(UNICORN_LINK_LIBRARIES ${UNICORN_LINK_LIBRARIES} pthread) + if(BUILD_SHARED_LIBS) + target_compile_definitions(unicorn PRIVATE UNICORN_SHARED) + set_property(TARGET unicorn APPEND_STRING PROPERTY LINK_FLAGS + " -Wl,--exclude-all-symbols") + endif() endif() if(ATOMIC_LINKAGE_FIX) @@ -1537,22 +1557,100 @@ if (UNICORN_LEGACY_STATIC_ARCHIVE) endif() if(UNICORN_FUZZ) - set(UNICORN_FUZZ_SUFFIX "arm_arm;arm_armbe;arm_thumb;arm64_arm;arm64_armbe;m68k_be;mips_32be;mips_32le;sparc_32be;x86_16;x86_32;x86_64;s390x_be") - if (NOT APPLE) + set(UNICORN_FUZZ_SUFFIX) + if(UNICORN_HAS_ARM) + list(APPEND UNICORN_FUZZ_SUFFIX + arm_arm arm_armbe arm_thumb) + endif() + if(UNICORN_HAS_AARCH64) + list(APPEND UNICORN_FUZZ_SUFFIX arm64_arm arm64_armbe) + endif() + if(UNICORN_HAS_M68K) + list(APPEND UNICORN_FUZZ_SUFFIX m68k_be) + endif() + if(UNICORN_HAS_MIPS) + list(APPEND UNICORN_FUZZ_SUFFIX + mips_32be mips_32le mips_64be mips_64le) + endif() + if(UNICORN_HAS_PPC) + list(APPEND UNICORN_FUZZ_SUFFIX ppc_32be ppc_64be) + endif() + if(UNICORN_HAS_RISCV) + list(APPEND UNICORN_FUZZ_SUFFIX riscv_32le riscv_64le) + endif() + if(UNICORN_HAS_SPARC) + list(APPEND UNICORN_FUZZ_SUFFIX sparc_32be sparc_64be) + endif() + if(UNICORN_HAS_TRICORE) + list(APPEND UNICORN_FUZZ_SUFFIX tricore_le) + endif() + if(UNICORN_HAS_X86) + list(APPEND UNICORN_FUZZ_SUFFIX x86_16 x86_32 x86_64) + endif() + if(UNICORN_HAS_S390X) + list(APPEND UNICORN_FUZZ_SUFFIX s390x_be) + endif() + if (UNIX AND NOT APPLE AND NOT ANDROID_ABI) set(SAMPLES_LIB ${SAMPLES_LIB} rt) endif() + if(UNICORN_FUZZ_LIBFUZZER) + if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang") + message(FATAL_ERROR + "UNICORN_FUZZ_LIBFUZZER requires a Clang compiler") + endif() + foreach(FUZZ_LIBRARY unicorn unicorn-common ${UNICORN_LINK_LIBRARIES}) + if(TARGET ${FUZZ_LIBRARY}) + target_compile_options(${FUZZ_LIBRARY} PRIVATE + -fsanitize=fuzzer-no-link) + endif() + endforeach() + set(UNICORN_FUZZ_DRIVER) + else() + set(UNICORN_FUZZ_DRIVER + ${CMAKE_CURRENT_SOURCE_DIR}/tests/fuzz/onedir.c) + endif() foreach(SUFFIX ${UNICORN_FUZZ_SUFFIX}) add_executable(fuzz_emu_${SUFFIX} ${CMAKE_CURRENT_SOURCE_DIR}/tests/fuzz/fuzz_emu_${SUFFIX}.c - ${CMAKE_CURRENT_SOURCE_DIR}/tests/fuzz/onedir.c + ${UNICORN_FUZZ_DRIVER} ) target_link_libraries(fuzz_emu_${SUFFIX} PRIVATE ${SAMPLES_LIB} ) + if(UNICORN_FUZZ_LIBFUZZER) + target_compile_options(fuzz_emu_${SUFFIX} PRIVATE + -fsanitize=fuzzer-no-link) + set_property(TARGET fuzz_emu_${SUFFIX} APPEND_STRING + PROPERTY LINK_FLAGS " -fsanitize=fuzzer") + endif() endforeach() + if(UNICORN_HAS_X86) + add_executable(fuzz_uc_api + ${CMAKE_CURRENT_SOURCE_DIR}/tests/fuzz/fuzz_uc_api.c + ${UNICORN_FUZZ_DRIVER} + ) + target_link_libraries(fuzz_uc_api PRIVATE + ${SAMPLES_LIB} + ) + if(UNICORN_FUZZ_LIBFUZZER) + target_compile_options(fuzz_uc_api PRIVATE + -fsanitize=fuzzer-no-link) + set_property(TARGET fuzz_uc_api APPEND_STRING + PROPERTY LINK_FLAGS " -fsanitize=fuzzer") + endif() + endif() endif() if(UNICORN_BUILD_TESTS) + include(cmake/Acutest.cmake) + if(UNICORN_HAS_X86 AND NOT ANDROID_ABI) + target_compile_definitions(unicorn PRIVATE + UNICORN_TEST_ALLOC_FAILURE) + if(TARGET unicorn_static) + target_compile_definitions(unicorn_static PRIVATE + UNICORN_TEST_ALLOC_FAILURE) + endif() + endif() set(UNICORN_CTEST_PARALLEL_LEVEL "40" CACHE STRING "Default CTest parallel job count") if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.17 AND NOT CMAKE_CTEST_ARGUMENTS) @@ -1590,18 +1688,119 @@ if(UNICORN_BUILD_TESTS) target_link_libraries(${TEST_FILE} PRIVATE ${SAMPLES_LIB} ) - add_test(${TEST_FILE} ${TEST_FILE}) if(ANDROID_ABI) file(APPEND ${CMAKE_BINARY_DIR}/adb.sh "adb push ${TEST_FILE} /data/local/tmp/build/\n") file(APPEND ${CMAKE_BINARY_DIR}/adb.sh "adb shell \"chmod +x /data/local/tmp/build/${TEST_FILE}\"\n") - file(APPEND ${CMAKE_BINARY_DIR}/adb.sh "adb shell \'LD_LIBRARY_PATH=/data/local/tmp/build:$LD_LIBRARY_PATH /data/local/tmp/build/${TEST_FILE}\' || exit -1\n") endif() + string(REGEX REPLACE "^test_" "" TEST_ARCHITECTURE "${TEST_FILE}") + unicorn_discover_acutest( + ${TEST_FILE} + 900 + "unit;${TEST_ARCHITECTURE}" + ) if (UNICORN_TARGET_ARCH STREQUAL "aarch64" OR UNICORN_TARGET_ARCH STREQUAL "ppc") target_compile_definitions(${TEST_FILE} PRIVATE TARGET_READ_INLINED) endif() endforeach() - if(UNICORN_TEST_FILE) - add_dependencies(test_parallel ${UNICORN_TEST_FILE}) + if(UNICORN_HAS_ARM AND NOT ANDROID_ABI) + add_executable(test_arm_m_profile_internal + ${CMAKE_CURRENT_SOURCE_DIR}/tests/internal/test_arm_m_profile.c + ) + target_compile_definitions(test_arm_m_profile_internal PRIVATE + NEED_CPU_H + ) + target_compile_options(test_arm_m_profile_internal PRIVATE + ${UNICORN_COMPILE_OPTIONS} + ) + target_include_directories(test_arm_m_profile_internal PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/tests/unit + ${CMAKE_CURRENT_SOURCE_DIR}/qemu/target/arm + ) + if(MSVC) + target_compile_options(test_arm_m_profile_internal PRIVATE + /FIarm.h + ) + target_include_directories(test_arm_m_profile_internal PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/msvc/arm-softmmu + ) + else() + target_compile_options(test_arm_m_profile_internal PRIVATE + -include arm.h + ) + target_include_directories(test_arm_m_profile_internal PRIVATE + ${CMAKE_BINARY_DIR}/arm-softmmu + ) + endif() + target_link_libraries(test_arm_m_profile_internal PRIVATE + ${SAMPLES_LIB} + ) + unicorn_discover_acutest( + test_arm_m_profile_internal + 900 + "unit;internal;arm" + ) + add_dependencies(test_parallel test_arm_m_profile_internal) + endif() + if(UNICORN_HAS_X86 AND NOT ANDROID_ABI) + add_executable(test_alloc_failure + ${CMAKE_CURRENT_SOURCE_DIR}/tests/internal/test_alloc_failure.c + ) + target_compile_options(test_alloc_failure PRIVATE + ${UNICORN_COMPILE_OPTIONS} + ) + target_include_directories(test_alloc_failure PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/tests/unit + ) + target_link_libraries(test_alloc_failure PRIVATE + ${SAMPLES_LIB} + ) + unicorn_discover_acutest( + test_alloc_failure + 900 + "unit;internal;memory" + ) + add_dependencies(test_parallel test_alloc_failure) + endif() + include(${CMAKE_CURRENT_SOURCE_DIR}/tests/regress/CMakeLists.txt) + if(UNICORN_TEST_PYTHON_REGRESSIONS) + if(CMAKE_CROSSCOMPILING OR NOT BUILD_SHARED_LIBS) + message(STATUS + "Python regressions require a native shared-library build") + else() + if(POLICY CMP0148) + cmake_policy(SET CMP0148 OLD) + endif() + find_package(PythonInterp 3.7 REQUIRED) + file(GLOB PYTHON_REGRESSION_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/tests/regress/*.py) + foreach(PYTHON_REGRESSION ${PYTHON_REGRESSION_FILES}) + get_filename_component(PYTHON_REGRESSION_NAME + ${PYTHON_REGRESSION} NAME_WE) + if(PYTHON_REGRESSION_NAME STREQUAL "regress") + continue() + endif() + add_test( + NAME python-regression.${PYTHON_REGRESSION_NAME} + COMMAND ${PYTHON_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tests/regress/runner/run_python_regression.py + --binding ${CMAKE_CURRENT_SOURCE_DIR}/bindings/python + --library $ + --test ${PYTHON_REGRESSION} + --timeout 60 + ) + set_tests_properties( + python-regression.${PYTHON_REGRESSION_NAME} PROPERTIES + LABELS "python-regression" + TIMEOUT 75 + ) + endforeach() + endif() + endif() + if(UNICORN_TEST_FILE OR UNICORN_REGRESSION_TARGETS) + add_dependencies(test_parallel + ${UNICORN_TEST_FILE} + ${UNICORN_REGRESSION_TARGETS} + ) endif() endif() diff --git a/README.md b/README.md index 30f67fed05..d8c611c8c9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Unicorn Engine +Unicorn Engin ============== [![pypi downloads](https://pepy.tech/badge/unicorn)](https://pepy.tech/project/unicorn) @@ -57,4 +57,4 @@ We also maintain a list of more challenged problems in [milestones](https://gith Please send pull request to our [dev branch](https://github.com/unicorn-engine/unicorn/tree/dev). -[CREDITS.TXT](CREDITS.TXT) records important contributors of our project. \ No newline at end of file +[CREDITS.TXT](CREDITS.TXT) records important contributors of our project. diff --git a/bindings/go/unicorn/context.go b/bindings/go/unicorn/context.go index 3e8bcbfe43..4641070dec 100644 --- a/bindings/go/unicorn/context.go +++ b/bindings/go/unicorn/context.go @@ -1,9 +1,6 @@ package unicorn -import ( - "runtime" - "unsafe" -) +import "runtime" // #include import "C" @@ -14,16 +11,33 @@ func (u *uc) ContextSave(reuse Context) (Context, error) { ctx := reuse if ctx == nil { ctx = new(*C.uc_context) + ucerr := C.uc_context_alloc(u.handle, ctx) + runtime.KeepAlive(u) + runtime.KeepAlive(ctx) + if err := errReturn(ucerr); err != nil { + return nil, err + } } - if err := errReturn(C.uc_context_alloc(u.handle, ctx)); err != nil { + ucerr := C.uc_context_save(u.handle, *ctx) + runtime.KeepAlive(u) + runtime.KeepAlive(ctx) + if err := errReturn(ucerr); err != nil { + if reuse == nil { + C.uc_context_free(*ctx) + runtime.KeepAlive(u) + runtime.KeepAlive(ctx) + } return nil, err } - runtime.SetFinalizer(ctx, func(p Context) { C.uc_free(unsafe.Pointer(*p)) }) - if err := errReturn(C.uc_context_save(u.handle, *ctx)); err != nil { + if reuse == nil { + runtime.SetFinalizer(ctx, func(p Context) { C.uc_context_free(*p) }) } return ctx, nil } func (u *uc) ContextRestore(ctx Context) error { - return errReturn(C.uc_context_restore(u.handle, *ctx)) + ucerr := C.uc_context_restore(u.handle, *ctx) + runtime.KeepAlive(u) + runtime.KeepAlive(ctx) + return errReturn(ucerr) } diff --git a/bindings/go/unicorn/context_test.go b/bindings/go/unicorn/context_test.go index 3231ef43c8..8777143f22 100644 --- a/bindings/go/unicorn/context_test.go +++ b/bindings/go/unicorn/context_test.go @@ -24,3 +24,46 @@ func TestContext(t *testing.T) { t.Fatal("context restore failed") } } + +func TestContextReuse(t *testing.T) { + u, err := NewUnicorn(ARCH_X86, MODE_32) + if err != nil { + t.Fatal(err) + } + if err := u.RegWrite(X86_REG_EBP, 100); err != nil { + t.Fatal(err) + } + ctx, err := u.ContextSave(nil) + if err != nil { + t.Fatal(err) + } + nativeCtx := *ctx + + if err := u.RegWrite(X86_REG_EBP, 200); err != nil { + t.Fatal(err) + } + reused, err := u.ContextSave(ctx) + if err != nil { + t.Fatal(err) + } + if reused != ctx { + t.Fatal("context reuse returned a different Go context") + } + if *reused != nativeCtx { + t.Fatal("context reuse replaced the native context") + } + + if err := u.RegWrite(X86_REG_EBP, 300); err != nil { + t.Fatal(err) + } + if err := u.ContextRestore(reused); err != nil { + t.Fatal(err) + } + val, err := u.RegRead(X86_REG_EBP) + if err != nil { + t.Fatal(err) + } + if val != 200 { + t.Fatal("context reuse did not update the saved state") + } +} diff --git a/bindings/haskell/src/Unicorn/Internal/Unicorn.chs b/bindings/haskell/src/Unicorn/Internal/Unicorn.chs index db9755204d..c12e80d3d3 100644 --- a/bindings/haskell/src/Unicorn/Internal/Unicorn.chs +++ b/bindings/haskell/src/Unicorn/Internal/Unicorn.chs @@ -119,7 +119,7 @@ instance Storable MemoryRegion where -- | Opaque storage for CPU context, used with the context functions. {# pointer *uc_context as Context - foreign finalizer uc_free_wrapper as memFree + foreign finalizer uc_context_free_wrapper as contextFree newtype #} @@ -127,11 +127,11 @@ instance Storable MemoryRegion where {# pointer *uc_context as ContextPtr -> Context #} -- | Make a CPU context out of a context pointer. The returned CPU context will --- automatically call 'uc_free' when it goes out of scope. +-- automatically call 'uc_context_free' when it goes out of scope. mkContext :: ContextPtr -> IO Context mkContext ptr = - liftM Context (newForeignPtr memFree ptr) + liftM Context (newForeignPtr contextFree ptr) ------------------------------------------------------------------------------- -- Emulator control diff --git a/bindings/haskell/src/cbits/unicorn_wrapper.c b/bindings/haskell/src/cbits/unicorn_wrapper.c index 878518ee49..b5287c5d5b 100644 --- a/bindings/haskell/src/cbits/unicorn_wrapper.c +++ b/bindings/haskell/src/cbits/unicorn_wrapper.c @@ -45,6 +45,6 @@ uc_err uc_reg_read_batch_wrapper(uc_engine *uc, int *regs, int64_t *vals, int co return ret; } -void uc_free_wrapper(void *mem) { - uc_free(mem); +void uc_context_free_wrapper(uc_context *context) { + uc_context_free(context); } diff --git a/bindings/haskell/src/include/unicorn_wrapper.h b/bindings/haskell/src/include/unicorn_wrapper.h index 31757163a1..36f7d12924 100644 --- a/bindings/haskell/src/include/unicorn_wrapper.h +++ b/bindings/haskell/src/include/unicorn_wrapper.h @@ -23,8 +23,8 @@ uc_err uc_reg_write_batch_wrapper(uc_engine *uc, int *regs, int64_t *vals, int c uc_err uc_reg_read_batch_wrapper(uc_engine *uc, int *regs, int64_t *vals, int count); /* - * Wrap Unicorn's uc_free function and ignore the returned error code. + * Wrap Unicorn's uc_context_free function and ignore the returned error code. */ -void uc_free_wrapper(void *context); +void uc_context_free_wrapper(uc_context *context); #endif diff --git a/bindings/pascal/examples/x86.lpr b/bindings/pascal/examples/x86.lpr index 6a24f68eef..88e4ec53e7 100644 --- a/bindings/pascal/examples/x86.lpr +++ b/bindings/pascal/examples/x86.lpr @@ -750,7 +750,7 @@ procedure test_i386_context_save(); uc_reg_read(uc, UC_X86_REG_EAX, @r_eax); WriteLn(Format('>>> EAX = 0x%x', [r_eax])); - err := uc_free(context); + err := uc_context_free(context); if (err <> UC_ERR_OK) then begin WriteLn(Format('Failed on uc_free() with error returned %u: %s', [err, uc_strerror(err)])); exit; diff --git a/bindings/pascal/unicorn/Unicorn_dyn.pas b/bindings/pascal/unicorn/Unicorn_dyn.pas index 96f296587c..eaa38b1643 100755 --- a/bindings/pascal/unicorn/Unicorn_dyn.pas +++ b/bindings/pascal/unicorn/Unicorn_dyn.pas @@ -483,7 +483,7 @@ function (uc : uc_engine; var hh : uc_hook; _type : integer; @uc: handle returned by uc_open() @context: pointer to a uc_engine*. This will be updated with the pointer to the new context on successful return of this function. - Later, this allocated memory must be freed with uc_free(). + Later, this allocated memory must be freed with uc_context_free(). @return UC_ERR_OK on success, or other value on failure (refer to uc_err enum for detailed error). @@ -491,10 +491,9 @@ function (uc : uc_engine; var hh : uc_hook; _type : integer; uc_context_alloc : function ( uc : uc_engine; var context : uc_context) : uc_err; cdecl ; (* - Free the memory allocated by uc_context_alloc & uc_mem_regions. + Free the memory allocated by uc_mem_regions. - @mem: memory allocated by uc_context_alloc (returned in *context), or - by uc_mem_regions (returned in *regions) + @mem: memory allocated by uc_mem_regions (returned in *regions) @return UC_ERR_OK on success, or other value on failure (refer to uc_err enum \ for detailed error). @@ -528,6 +527,16 @@ function (uc : uc_engine; var hh : uc_hook; _type : integer; *) uc_context_restore : function(uc : uc_engine; context : uc_context) : uc_err; cdecl; +(* + Free the context allocated by uc_context_alloc(). + + @context: handle returned by uc_context_alloc() + + @return UC_ERR_OK on success, or other value on failure (refer to uc_err enum + for detailed error). +*) + uc_context_free : function(context : uc_context) : uc_err; cdecl; + {============================= Global Functions ================================} @@ -639,6 +648,9 @@ function loadUC(): Boolean; @uc_context_restore := dyn_loadfunc('uc_context_restore'); if (@uc_context_restore = nil) then exit(false); + @uc_context_free := dyn_loadfunc('uc_context_free'); + if (@uc_context_free = nil) then exit(false); + @uc_free := dyn_loadfunc('uc_free'); if (@uc_free = nil) then exit(false); diff --git a/bindings/python/tests/test_network_auditing.py b/bindings/python/tests/test_network_auditing.py index b137cf6460..36f7e72a9b 100755 --- a/bindings/python/tests/test_network_auditing.py +++ b/bindings/python/tests/test_network_auditing.py @@ -360,7 +360,7 @@ def hook_intr(uc, intno, user_data): # Test X86 32 bit -def test_i386(code): +def run_i386(code): global fd_chains fd_chains.clean() @@ -401,7 +401,7 @@ def test_i386(code): id_gen = IdGenerator() if __name__ == '__main__': - test_i386(X86_SEND_ETCPASSWD) - test_i386(X86_BIND_TCP) - test_i386(X86_REVERSE_TCP) - test_i386(X86_REVERSE_TCP_2) + run_i386(X86_SEND_ETCPASSWD) + run_i386(X86_BIND_TCP) + run_i386(X86_REVERSE_TCP) + run_i386(X86_REVERSE_TCP_2) diff --git a/bindings/python/tests/test_shellcode.py b/bindings/python/tests/test_shellcode.py index ca16c184af..d9bac06d21 100755 --- a/bindings/python/tests/test_shellcode.py +++ b/bindings/python/tests/test_shellcode.py @@ -133,7 +133,7 @@ def hook_syscall64(mu, user_data): # Test X86 32 bit -def test_i386(mode, code): +def run_i386(mode, code): if mode == UC_MODE_32: print("Emulate x86_32 code") elif mode == UC_MODE_64: @@ -177,8 +177,8 @@ def test_i386(mode, code): if __name__ == '__main__': - test_i386(UC_MODE_32, X86_CODE32_SELF) + run_i386(UC_MODE_32, X86_CODE32_SELF) print("=" * 20) - test_i386(UC_MODE_32, X86_CODE32) + run_i386(UC_MODE_32, X86_CODE32) print("=" * 20) - test_i386(UC_MODE_64, X86_CODE64) + run_i386(UC_MODE_64, X86_CODE64) diff --git a/bindings/python/unicorn/unicorn_py2.py b/bindings/python/unicorn/unicorn_py2.py index 0b9f560c1b..4bd7cf7073 100644 --- a/bindings/python/unicorn/unicorn_py2.py +++ b/bindings/python/unicorn/unicorn_py2.py @@ -168,7 +168,13 @@ class uc_tb(ctypes.Structure): _setup_prototype(_uc, "uc_context_restore", ucerr, uc_engine, uc_context) _setup_prototype(_uc, "uc_context_size", ctypes.c_size_t, uc_engine) _setup_prototype(_uc, "uc_context_reg_read", ucerr, uc_context, ctypes.c_int, ctypes.c_void_p) +_setup_prototype(_uc, "uc_context_reg_read_batch", ucerr, uc_context, + ctypes.POINTER(ctypes.c_int), + ctypes.POINTER(ctypes.c_void_p), ctypes.c_int) _setup_prototype(_uc, "uc_context_reg_write", ucerr, uc_context, ctypes.c_int, ctypes.c_void_p) +_setup_prototype(_uc, "uc_context_reg_write_batch", ucerr, uc_context, + ctypes.POINTER(ctypes.c_int), + ctypes.POINTER(ctypes.c_void_p), ctypes.c_int) _setup_prototype(_uc, "uc_context_free", ucerr, uc_context) _setup_prototype(_uc, "uc_mem_regions", ucerr, uc_engine, ctypes.POINTER(ctypes.POINTER(_uc_mem_region)), ctypes.POINTER(ctypes.c_uint32)) @@ -244,42 +250,31 @@ def uc_arch_supported(query): # uc_reg_read/write and uc_context_reg_read/write. -def reg_read(reg_read_func, arch, reg_id, opt=None): +def _reg_read_arg(arch, reg_id, opt=None): if arch == uc.UC_ARCH_X86: if reg_id in [x86_const.UC_X86_REG_IDTR, x86_const.UC_X86_REG_GDTR, x86_const.UC_X86_REG_LDTR, x86_const.UC_X86_REG_TR]: reg = uc_x86_mmr() - status = reg_read_func(reg_id, ctypes.byref(reg)) - if status != uc.UC_ERR_OK: - raise UcError(status) - return reg.selector, reg.base, reg.limit, reg.flags + return reg, lambda value: ( + value.selector, value.base, value.limit, value.flags) if reg_id in xrange(x86_const.UC_X86_REG_FP0, x86_const.UC_X86_REG_FP0 + 8): reg = uc_x86_float80() - status = reg_read_func(reg_id, ctypes.byref(reg)) - if status != uc.UC_ERR_OK: - raise UcError(status) - return reg.mantissa, reg.exponent + return reg, lambda value: (value.mantissa, value.exponent) if reg_id in xrange(x86_const.UC_X86_REG_XMM0, x86_const.UC_X86_REG_XMM0 + 8): reg = uc_x86_xmm() - status = reg_read_func(reg_id, ctypes.byref(reg)) - if status != uc.UC_ERR_OK: - raise UcError(status) - return reg.low_qword | (reg.high_qword << 64) + return reg, lambda value: ( + value.low_qword | (value.high_qword << 64)) if reg_id in xrange(x86_const.UC_X86_REG_YMM0, x86_const.UC_X86_REG_YMM0 + 16): reg = uc_x86_ymm() - status = reg_read_func(reg_id, ctypes.byref(reg)) - if status != uc.UC_ERR_OK: - raise UcError(status) - return reg.first_qword | (reg.second_qword << 64) | (reg.third_qword << 128) | (reg.fourth_qword << 192) + return reg, lambda value: ( + value.first_qword | (value.second_qword << 64) | + (value.third_qword << 128) | (value.fourth_qword << 192)) if reg_id is x86_const.UC_X86_REG_MSR: if opt is None: raise UcError(uc.UC_ERR_ARG) reg = uc_x86_msr() reg.rid = opt - status = reg_read_func(reg_id, ctypes.byref(reg)) - if status != uc.UC_ERR_OK: - raise UcError(status) - return reg.value + return reg, lambda value: value.value if arch == uc.UC_ARCH_ARM: if reg_id == arm_const.UC_ARM_REG_CP_REG: @@ -287,10 +282,7 @@ def reg_read(reg_read_func, arch, reg_id, opt=None): if not isinstance(opt, tuple) or len(opt) != 7: raise UcError(uc.UC_ERR_ARG) reg.cp, reg.is64, reg.sec, reg.crn, reg.crm, reg.opc1, reg.opc2 = opt - status = reg_read_func(reg_id, ctypes.byref(reg)) - if status != uc.UC_ERR_OK: - raise UcError(status) - return reg.val + return reg, lambda value: value.val if arch == uc.UC_ARCH_ARM64: if reg_id == arm64_const.UC_ARM64_REG_CP_REG: @@ -298,28 +290,53 @@ def reg_read(reg_read_func, arch, reg_id, opt=None): if not isinstance(opt, tuple) or len(opt) != 5: raise UcError(uc.UC_ERR_ARG) reg.crn, reg.crm, reg.op0, reg.op1, reg.op2 = opt - status = reg_read_func(reg_id, ctypes.byref(reg)) - if status != uc.UC_ERR_OK: - raise UcError(status) - return reg.val + return reg, lambda value: value.val - elif reg_id in xrange(arm64_const.UC_ARM64_REG_Q0, arm64_const.UC_ARM64_REG_Q31 + 1) or xrange( - arm64_const.UC_ARM64_REG_V0, arm64_const.UC_ARM64_REG_V31 + 1): + elif (reg_id in xrange(arm64_const.UC_ARM64_REG_Q0, + arm64_const.UC_ARM64_REG_Q31 + 1) or + reg_id in xrange(arm64_const.UC_ARM64_REG_V0, + arm64_const.UC_ARM64_REG_V31 + 1)): reg = uc_arm64_neon128() - status = reg_read_func(reg_id, ctypes.byref(reg)) - if status != uc.UC_ERR_OK: - raise UcError(status) - return reg.low_qword | (reg.high_qword << 64) + return reg, lambda value: ( + value.low_qword | (value.high_qword << 64)) # read to 64bit number to be safe - reg = ctypes.c_uint64(0) + return ctypes.c_uint64(0), lambda value: value.value + + +def reg_read(reg_read_func, arch, reg_id, opt=None): + reg, get_value = _reg_read_arg(arch, reg_id, opt) status = reg_read_func(reg_id, ctypes.byref(reg)) if status != uc.UC_ERR_OK: raise UcError(status) - return reg.value + return get_value(reg) + + +def reg_read_batch(reg_read_batch_func, arch, reg_data): + count = len(reg_data) + reg_ids = [] + values = [] + get_values = [] + + for elem in reg_data: + reg_id, opt = elem if isinstance(elem, tuple) else (elem, None) + value, get_value = _reg_read_arg(arch, reg_id, opt) + reg_ids.append(reg_id) + values.append(value) + get_values.append(get_value) + + reg_list = (ctypes.c_int * count)(*reg_ids) + ptr_list = (ctypes.c_void_p * count)(*( + ctypes.c_void_p(ctypes.addressof(value)) for value in values)) + status = reg_read_batch_func(reg_list, ptr_list, ctypes.c_int(count)) + if status != uc.UC_ERR_OK: + raise UcError(status) + return tuple(get_value(value) for value, get_value in + zip(values, get_values)) -def reg_write(reg_write_func, arch, reg_id, value): + +def _reg_write_arg(arch, reg_id, value): reg = None if arch == uc.UC_ARCH_X86: @@ -351,20 +368,22 @@ def reg_write(reg_write_func, arch, reg_id, value): reg.value = value[1] if arch == uc.UC_ARCH_ARM64: - if reg_id in xrange(arm64_const.UC_ARM64_REG_Q0, arm64_const.UC_ARM64_REG_Q31 + 1) or xrange( - arm64_const.UC_ARM64_REG_V0, arm64_const.UC_ARM64_REG_V31 + 1): - reg = uc_arm64_neon128() - reg.low_qword = value & 0xffffffffffffffff - reg.high_qword = value >> 64 - - if arch == uc.UC_ARCH_ARM: if reg_id == arm64_const.UC_ARM64_REG_CP_REG: reg = uc_arm64_cp_reg() if not isinstance(value, tuple) or len(value) != 6: raise UcError(uc.UC_ERR_ARG) reg.crn, reg.crm, reg.op0, reg.op1, reg.op2, reg.val = value - elif reg_id == arm_const.UC_ARM_REG_CP_REG: + elif (reg_id in xrange(arm64_const.UC_ARM64_REG_Q0, + arm64_const.UC_ARM64_REG_Q31 + 1) or + reg_id in xrange(arm64_const.UC_ARM64_REG_V0, + arm64_const.UC_ARM64_REG_V31 + 1)): + reg = uc_arm64_neon128() + reg.low_qword = value & 0xffffffffffffffff + reg.high_qword = value >> 64 + + if arch == uc.UC_ARCH_ARM: + if reg_id == arm_const.UC_ARM_REG_CP_REG: reg = uc_arm_cp_reg() if not isinstance(value, tuple) or len(value) != 8: raise UcError(uc.UC_ERR_ARG) @@ -374,6 +393,11 @@ def reg_write(reg_write_func, arch, reg_id, value): # convert to 64bit number to be safe reg = ctypes.c_uint64(value) + return reg + + +def reg_write(reg_write_func, arch, reg_id, value): + reg = _reg_write_arg(arch, reg_id, value) status = reg_write_func(reg_id, ctypes.byref(reg)) if status != uc.UC_ERR_OK: raise UcError(status) @@ -381,6 +405,26 @@ def reg_write(reg_write_func, arch, reg_id, value): return +def reg_write_batch(reg_write_batch_func, arch, reg_data): + reg_ids = [] + values = [] + + for elem in reg_data: + reg_id, value = elem + reg_ids.append(reg_id) + values.append(_reg_write_arg(arch, reg_id, value)) + + count = len(reg_ids) + reg_list = (ctypes.c_int * count)(*reg_ids) + ptr_list = (ctypes.c_void_p * count)(*( + ctypes.c_void_p(ctypes.addressof(value)) for value in values)) + status = reg_write_batch_func(reg_list, ptr_list, ctypes.c_int(count)) + if status != uc.UC_ERR_OK: + raise UcError(status) + + return + + def _catch_hook_exception(func): @wraps(func) def wrapper(self, *args, **kwargs): @@ -971,19 +1015,46 @@ def mode(self): def reg_read(self, reg_id, opt=None): return reg_read(partial(_uc.uc_context_reg_read, self._context), self.arch, reg_id, opt) + # return the values of multiple registers + def reg_read_batch(self, reg_data): + return reg_read_batch( + partial(_uc.uc_context_reg_read_batch, self._context), + self.arch, reg_data) + # write to a register def reg_write(self, reg_id, value): return reg_write(partial(_uc.uc_context_reg_write, self._context), self.arch, reg_id, value) + # write to multiple registers + def reg_write_batch(self, reg_data): + return reg_write_batch( + partial(_uc.uc_context_reg_write_batch, self._context), + self.arch, reg_data) + # Make UcContext picklable def __getstate__(self): - return (bytes(self), self.size, self.arch, self.mode) + return (ctypes.string_at(self.context, self.size), self.size, + self.arch, self.mode) def __setstate__(self, state): - self._size = state[1] - self._context = ctypes.cast(ctypes.create_string_buffer(state[0], self._size), uc_context) - # __init__ won'e be invoked, so we are safe to set it here. + # __init__ won't be invoked for unpickled objects. self._to_free = False + self._size = state[1] + if (self._size != len(state[0]) or + self._size < ctypes.sizeof(ctypes.c_size_t)): + raise ValueError("Invalid Unicorn context") + + context_size = ctypes.c_size_t.from_buffer_copy(state[0]).value + if context_size > self._size: + raise ValueError("Invalid Unicorn context") + + header_size = self._size - context_size + self._context_buffer = ctypes.create_string_buffer(self._size + 15) + buffer_address = ctypes.addressof(self._context_buffer) + offset = -(buffer_address + header_size) & 15 + context_address = buffer_address + offset + ctypes.memmove(context_address, state[0], self._size) + self._context = ctypes.cast(context_address, uc_context) self._arch = state[2] self._mode = state[3] diff --git a/bindings/python/unicorn/unicorn_py3/unicorn.py b/bindings/python/unicorn/unicorn_py3/unicorn.py index c86d72a6c3..b553d4dd15 100644 --- a/bindings/python/unicorn/unicorn_py3/unicorn.py +++ b/bindings/python/unicorn/unicorn_py3/unicorn.py @@ -1573,6 +1573,24 @@ def mode(self) -> int: return self._mode + def _select_reg_class(self, reg_id: int) -> Type: + if self.arch == uc.UC_ARCH_ARM: + from .arch.arm import UcAArch32 + + return UcAArch32._select_reg_class(reg_id) + + if self.arch == uc.UC_ARCH_ARM64: + from .arch.arm64 import UcAArch64 + + return UcAArch64._select_reg_class(reg_id) + + if self.arch == uc.UC_ARCH_X86: + from .arch.intel import UcIntel + + return UcIntel._select_reg_class(reg_id) + + return super()._select_reg_class(reg_id) + # RegStateManager mixin method implementation def _do_reg_read(self, reg_id: int, reg_obj) -> int: """Private register read implementation. @@ -1606,7 +1624,20 @@ def __getstate__(self) -> Tuple[bytes, int, int, int]: def __setstate__(self, state: Tuple[bytes, int, int, int]) -> None: context, size, arch, mode = state - self._context = ctypes.cast(ctypes.create_string_buffer(context, size), uc_context) + if size != len(context) or size < ctypes.sizeof(ctypes.c_size_t): + raise ValueError("Invalid Unicorn context") + + context_size = ctypes.c_size_t.from_buffer_copy(context).value + if context_size > size: + raise ValueError("Invalid Unicorn context") + + header_size = size - context_size + self._context_buffer = ctypes.create_string_buffer(size + 15) + buffer_address = ctypes.addressof(self._context_buffer) + offset = -(buffer_address + header_size) & 15 + context_address = buffer_address + offset + ctypes.memmove(context_address, context, size) + self._context = ctypes.cast(context_address, uc_context) self._size = size self._arch = arch self._mode = mode diff --git a/bindings/ruby/unicorn_gem/ext/unicorn.c b/bindings/ruby/unicorn_gem/ext/unicorn.c index 7bce97b2cf..8e17841d8f 100644 --- a/bindings/ruby/unicorn_gem/ext/unicorn.c +++ b/bindings/ruby/unicorn_gem/ext/unicorn.c @@ -30,6 +30,10 @@ VALUE UcError = Qnil; VALUE SavedContext = Qnil; VALUE Hook = Qnil; +static void uc_context_free_wrapper(void *context) { + uc_context_free((uc_context *)context); +} + void Init_unicorn_engine(void) { rb_require("unicorn_engine/unicorn_const"); @@ -556,7 +560,8 @@ VALUE m_uc_context_save(VALUE self){ rb_raise(UcError, "%s", uc_strerror(err)); } - VALUE sc = Data_Wrap_Struct(SavedContext, 0, uc_free, _context); + VALUE sc = Data_Wrap_Struct(SavedContext, 0, uc_context_free_wrapper, + _context); return sc; } diff --git a/bindings/vb6/main.cpp b/bindings/vb6/main.cpp index fb05c5b43c..36c23aa907 100644 --- a/bindings/vb6/main.cpp +++ b/bindings/vb6/main.cpp @@ -287,6 +287,11 @@ uc_err __stdcall ucs_context_alloc(uc_engine *uc, uc_context **context){ return uc_context_alloc(uc, context); } +uc_err __stdcall ucs_context_free(uc_context *context){ +#pragma EXPORT + return uc_context_free(context); +} + uc_err __stdcall ucs_free(void *mem){ #pragma EXPORT return uc_free(mem); diff --git a/bindings/vb6/ucIntel32.cls b/bindings/vb6/ucIntel32.cls index 680621cf1c..2871b84bef 100644 --- a/bindings/vb6/ucIntel32.cls +++ b/bindings/vb6/ucIntel32.cls @@ -643,7 +643,7 @@ Function saveContext() As Long If e <> uc_err_ok Then errMsg = err2str(e) - e = ucs_free(hContext) + e = ucs_context_free(hContext) If e <> uc_err_ok Then errMsg = errMsg & " error freeing context: " & err2str(e) Exit Function End If @@ -670,7 +670,7 @@ End Function Function freeContext(hContext As Long) As Boolean Dim e As uc_err - e = ucs_free(hContext) + e = ucs_context_free(hContext) If e <> uc_err_ok Then errMsg = err2str(e) Else diff --git a/bindings/vb6/uc_def.bas b/bindings/vb6/uc_def.bas index 83407a3504..adc39937ef 100644 --- a/bindings/vb6/uc_def.bas +++ b/bindings/vb6/uc_def.bas @@ -24,6 +24,7 @@ Option Explicit ' uc_mem_regions ' uc_mem_map_ptr ' uc_context_alloc +' uc_context_free ' uc_free ' uc_context_save ' uc_context_restore @@ -2322,6 +2323,9 @@ Public Declare Function ucs_context_alloc Lib "ucvbshim.dll" (ByVal hEngine As L ' for detailed error). '*/ 'UNICORN_EXPORT +'uc_err uc_context_free(uc_context *context); +Public Declare Function ucs_context_free Lib "ucvbshim.dll" (ByVal context As Long) As uc_err + 'uc_err uc_free(void* mem); Public Declare Function ucs_free Lib "ucvbshim.dll" (ByVal mem As Long) As uc_err diff --git a/cmake/Acutest.cmake b/cmake/Acutest.cmake new file mode 100644 index 0000000000..54a7b13301 --- /dev/null +++ b/cmake/Acutest.cmake @@ -0,0 +1,80 @@ +set(UNICORN_ACUTEST_MODULE_DIR "${CMAKE_CURRENT_LIST_DIR}") + +function(unicorn_discover_acutest target timeout labels) + if(ANDROID_ABI) + file(APPEND ${CMAKE_BINARY_DIR}/adb.sh + "adb shell 'LD_LIBRARY_PATH=/data/local/tmp/build:$LD_LIBRARY_PATH /data/local/tmp/build/${target}' || exit -1\n") + return() + endif() + + get_target_property(test_executor ${target} CROSSCOMPILING_EMULATOR) + if(test_executor MATCHES "-NOTFOUND$") + set(test_executor ${CMAKE_CROSSCOMPILING_EMULATOR}) + endif() + if(CMAKE_CROSSCOMPILING AND NOT test_executor) + message(STATUS + "Skipping Acutest discovery for ${target}: " + "no cross-compiling emulator is configured") + return() + endif() + + if(CMAKE_VERSION VERSION_LESS 3.10) + set(test_command ${test_executor} $) + add_test(NAME "unit.${target}" COMMAND ${test_command}) + set_tests_properties("unit.${target}" PROPERTIES + LABELS "${labels}" + TIMEOUT ${timeout} + ) + return() + endif() + + set(include_file + "${CMAKE_CURRENT_BINARY_DIR}/${target}_include.cmake") + get_property(generator_is_multi_config GLOBAL PROPERTY + GENERATOR_IS_MULTI_CONFIG) + if(generator_is_multi_config) + set(tests_file + "${CMAKE_CURRENT_BINARY_DIR}/${target}_tests-$.cmake") + set(byproducts_arg) + else() + set(tests_file + "${CMAKE_CURRENT_BINARY_DIR}/${target}_tests.cmake") + set(byproducts_arg BYPRODUCTS "${tests_file}") + endif() + set_property(TARGET ${target} APPEND PROPERTY LINK_DEPENDS + "${UNICORN_ACUTEST_MODULE_DIR}/AcutestAddTests.cmake") + add_custom_command(TARGET ${target} POST_BUILD + ${byproducts_arg} + COMMAND ${CMAKE_COMMAND} + "-DTEST_TARGET=${target}" + "-DTEST_EXECUTABLE=$" + "-DTEST_EXECUTOR=${test_executor}" + "-DTEST_TIMEOUT=${timeout}" + "-DTEST_LABELS=${labels}" + "-DCTEST_FILE=${tests_file}" + -P ${UNICORN_ACUTEST_MODULE_DIR}/AcutestAddTests.cmake + VERBATIM + ) + if(generator_is_multi_config) + file(WRITE "${include_file}" + "if(DEFINED CTEST_CONFIGURATION_TYPE AND NOT " + "CTEST_CONFIGURATION_TYPE STREQUAL \"\")\n" + " if(EXISTS \"${CMAKE_CURRENT_BINARY_DIR}/${target}_tests-" + "\${CTEST_CONFIGURATION_TYPE}.cmake\")\n" + " include(\"${CMAKE_CURRENT_BINARY_DIR}/${target}_tests-" + "\${CTEST_CONFIGURATION_TYPE}.cmake\")\n" + " else()\n" + " add_test(unit.${target}_NOT_BUILT unit.${target}_NOT_BUILT)\n" + " endif()\n" + "endif()\n" + ) + else() + file(WRITE "${include_file}" + "if(EXISTS [==[${tests_file}]==])\n" + " include([==[${tests_file}]==])\n" + "endif()\n" + ) + endif() + set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES + "${include_file}") +endfunction() diff --git a/cmake/AcutestAddTests.cmake b/cmake/AcutestAddTests.cmake new file mode 100644 index 0000000000..f92c4a6a1d --- /dev/null +++ b/cmake/AcutestAddTests.cmake @@ -0,0 +1,68 @@ +function(acutest_quote value output) + set(equals "=") + string(FIND "${value}" "]${equals}]" closing_bracket) + while(NOT closing_bracket EQUAL -1) + set(equals "${equals}=") + string(FIND "${value}" "]${equals}]" closing_bracket) + endwhile() + set(${output} "[${equals}[${value}]${equals}]" PARENT_SCOPE) +endfunction() + +if(NOT EXISTS "${TEST_EXECUTABLE}") + message(FATAL_ERROR "Acutest executable does not exist: ${TEST_EXECUTABLE}") +endif() + +execute_process( + COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" --list + TIMEOUT 30 + OUTPUT_VARIABLE test_output + ERROR_VARIABLE test_error + RESULT_VARIABLE test_result +) +if(NOT test_result EQUAL 0) + message(FATAL_ERROR + "Acutest discovery failed for ${TEST_TARGET}: ${test_error}") +endif() + +string(REPLACE "\r\n" "\n" test_output "${test_output}") +string(REPLACE "\r" "\n" test_output "${test_output}") +string(REPLACE ";" "\\;" test_output "${test_output}") +string(REPLACE "\n" ";" test_names "${test_output}") + +set(discovered_names) +set(test_script "") +foreach(test_name ${test_names}) + string(STRIP "${test_name}" test_name) + if(test_name STREQUAL "" OR test_name STREQUAL "Unit tests:") + continue() + endif() + list(FIND discovered_names "${test_name}" duplicate_index) + if(NOT duplicate_index EQUAL -1) + message(FATAL_ERROR + "Duplicate Acutest case ${test_name} in ${TEST_TARGET}") + endif() + list(APPEND discovered_names "${test_name}") + + set(ctest_name "unit.${TEST_TARGET}.${test_name}") + acutest_quote("${ctest_name}" quoted_ctest_name) + acutest_quote("${TEST_EXECUTABLE}" quoted_executable) + acutest_quote("${test_name}" quoted_test_name) + acutest_quote("${TEST_LABELS}" quoted_labels) + + set(quoted_executor "") + foreach(executor_argument ${TEST_EXECUTOR}) + acutest_quote("${executor_argument}" quoted_argument) + set(quoted_executor "${quoted_executor} ${quoted_argument}") + endforeach() + + set(test_script "${test_script}add_test(${quoted_ctest_name}${quoted_executor} ${quoted_executable} ${quoted_test_name})\n") + set(test_script "${test_script}set_tests_properties(${quoted_ctest_name} PROPERTIES LABELS ${quoted_labels} TIMEOUT ${TEST_TIMEOUT})\n") +endforeach() + +if(NOT discovered_names) + message(FATAL_ERROR "Acutest discovery found no cases in ${TEST_TARGET}") +endif() + +set(temporary_file "${CTEST_FILE}.tmp") +file(WRITE "${temporary_file}" "${test_script}") +file(RENAME "${temporary_file}" "${CTEST_FILE}") diff --git a/docs/Hooks.md b/docs/Hooks.md index c72c27fb8a..42a182df95 100644 --- a/docs/Hooks.md +++ b/docs/Hooks.md @@ -64,11 +64,52 @@ You might implement memory protection in a different manner than the standard Un *What is it?* -The `UC_HOOK_MEM_FETCH` hook is not used. +The `UC_HOOK_MEM_FETCH` hook reports successful instruction fetches as +runtime instruction events. It uses the `uc_cb_hookmem_t` callback type: + +```c +typedef void (*uc_cb_hookmem_t)(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, + void *user_data); +``` + +For this hook, `type` is `UC_MEM_FETCH`, `address` is the guest address of the +instruction, `size` is the decoded instruction size in bytes, and `value` is +0. The callback-visible program counter is synchronized to `address`. This is +an instruction-level event, not a callback for each internal memory read used +to translate a block; executing a cached translated block still reports the +instructions that reach their runtime execution gates. + +Normally, when both `UC_HOOK_MEM_FETCH` and `UC_HOOK_CODE` match an +instruction, the fetch callback runs first. A code callback which stops +emulation therefore does not suppress the fetch event already delivered for +that instruction. + +Architectural execution rules still apply. A32 conditional instructions keep +the existing code hook before the predicate check: a failed predicate reports +the code hook but no successful fetch, while a passed predicate reports the +code hook and then the fetch hook. A code hook which changes the condition +flags can consequently change whether that instruction executes and reports a +fetch. Thumb IT predicates are checked before both hooks, so a skipped +instruction reports neither hook and an executed instruction reports fetch +before code. A MIPS branch-likely delay slot which is annulled is also skipped +before both hooks; an executed delay slot reports one fetch event. + +An invalid encoding does not suppress a successful fetch if the instruction +reaches this runtime path. The callback is delivered with the decoded width +before emulation reports the architecture's invalid-instruction error. For +example, the current x86 path reports the two-byte invalid encoding before +returning `UC_ERR_INSN_INVALID`, while an invalid two-byte RISC-V compressed +encoding is reported before `UC_ERR_EXCEPTION`. A fetch which fails because +memory is unmapped or non-executable is instead handled by the corresponding +`UC_HOOK_MEM_FETCH_UNMAPPED` or `UC_HOOK_MEM_FETCH_PROT` hook. *Why might you use it?* -You wouldn't. It's deprecated and will never be called. +You might use it to trace the runtime instruction-fetch stream with each +instruction's address and decoded size, including variable-length +instructions, while retaining the architecture's predicate and annul +semantics. ## UC_HOOK_MEM_READ_UNMAPPED, UC_HOOK_MEM_WRITE_UNMAPPED, UC_HOOK_MEM_FETCH_UNMAPPED @@ -109,4 +150,4 @@ In all cases you can either map the page in with the `uc_mem_protect*` function *Why might you use it?* -You might change the protection level of the region to allow the memory to be accessed, or you might return non-0 to abort execution. \ No newline at end of file +You might change the protection level of the region to allow the memory to be accessed, or you might return non-0 to abort execution. diff --git a/include/uc_priv.h b/include/uc_priv.h index 6642b21838..aec167693e 100644 --- a/include/uc_priv.h +++ b/include/uc_priv.h @@ -9,12 +9,15 @@ #include #include "qemu.h" +#include "qemu/atomic.h" #include "qemu/xxhash.h" #include "unicorn/unicorn.h" #include "list.h" // The max recursive nested uc_emu_start levels #define UC_MAX_NESTED_LEVEL (64) +// uc_invalidate_tb owns one additional fault-catching frame. +#define UC_MAX_NESTED_JMP_LEVEL (UC_MAX_NESTED_LEVEL + 1) // These are masks of supported modes for each cpu/arch. // They should be updated when changes are made to the uc_mode enum typedef. @@ -72,6 +75,54 @@ typedef struct { reg_write_t write; } context_reg_rw_t; +typedef struct UcEmuFrame { + struct uc_struct *uc; + QemuThread timer; + int state; + int64_t start_time; + uint64_t timeout; + bool descendant_timed_out; + bool timer_started; +#ifdef _WIN32 + uintptr_t timer_handle; +#endif +} UcEmuFrame; + +typedef struct UcTbExecFrame { + struct TranslationBlock *tb; + bool active; + bool exit_requested; +} UcTbExecFrame; + +struct UcMapping { + struct uc_struct *owner; + uint64_t begin; + uint64_t size; + uint32_t perms; + uint32_t context_refs; + int32_t priority; + bool active; + MemoryRegion *root; + MemoryRegion *regions; + UcMapping *next; +}; + +typedef struct UcContextMapping { + UcMapping *mapping; + uint32_t first_region; + uint32_t region_count; +} UcContextMapping; + +typedef enum UcTestAllocFailSite { + UC_TEST_ALLOC_FAIL_NONE, + UC_TEST_ALLOC_FAIL_MAPPED_BLOCKS, + UC_TEST_ALLOC_FAIL_MAPPING_RECORD, + UC_TEST_ALLOC_FAIL_CONTEXT_VIEW, + UC_TEST_ALLOC_FAIL_CONTEXT_MAPPINGS, + UC_TEST_ALLOC_FAIL_CONTEXT_REGIONS, + UC_TEST_ALLOC_FAIL_RESTORE_VIEW, +} UcTestAllocFailSite; + typedef void (*reg_reset_t)(struct uc_struct *uc); typedef bool (*uc_write_mem_t)(AddressSpace *as, hwaddr addr, @@ -84,9 +135,9 @@ typedef bool (*uc_read_mem_virtual_t)(struct uc_struct *uc, vaddr addr, uint32_t prot, uint8_t *buf, int len); typedef bool (*uc_virtual_to_physical_t)(struct uc_struct *uc, vaddr addr, - uint32_t prot, uint64_t *res); + uint32_t prot, uint64_t *res); -typedef MemoryRegion *(*uc_mem_cow_t)(struct uc_struct *uc, +typedef MemoryRegion *(*uc_mem_cow_t)(struct uc_struct *uc, UcMapping *mapping, MemoryRegion *current, hwaddr begin, size_t size); @@ -108,7 +159,22 @@ typedef MemoryRegion *(*uc_args_uc_ram_size_ptr_t)(struct uc_struct *, hwaddr begin, size_t size, uint32_t perms, void *ptr); -typedef void (*uc_mem_unmap_t)(struct uc_struct *, MemoryRegion *mr); +typedef void (*uc_mem_unmap_t)(struct uc_struct *, UcMapping *mapping); + +typedef void (*uc_memory_move_t)(struct uc_struct *, UcMapping *mapping, + bool update_topology); + +typedef void (*uc_memory_restore_topology_t)(struct uc_struct *, + UcMapping *mapping, + MemoryRegion *const *regions, + uint32_t region_count, + bool update_topology); + +typedef void (*uc_memory_mapping_free_t)(UcMapping *mapping); + +typedef void (*uc_memory_mapping_prune_t)(UcMapping *mapping); + +typedef void (*uc_memory_mapping_normalize_t)(UcMapping *mapping); typedef MemoryRegion *(*uc_memory_mapping_t)(struct uc_struct *, hwaddr addr); @@ -117,6 +183,10 @@ typedef void (*uc_memory_filter_t)(MemoryRegion *, int32_t); typedef bool (*uc_flatview_copy_t)(struct uc_struct *, FlatView *, FlatView *, bool); +typedef bool (*uc_flatview_reserve_t)(FlatView *, unsigned int); + +typedef void (*uc_address_space_restore_flatview_t)(AddressSpace *, FlatView *); + typedef void (*uc_readonly_mem_t)(MemoryRegion *mr, bool readonly); typedef int (*uc_cpus_init)(struct uc_struct *, const char *); @@ -145,6 +215,13 @@ typedef void (*uc_softfloat_initialize)(void); // tcg flush softmmu tlb typedef void (*uc_tcg_flush_tlb)(struct uc_struct *uc); +typedef bool (*uc_tb_exec_frame_resolve_t)( + struct uc_struct *uc, const struct TranslationBlock *tb, + uint64_t phys_start[2], uint32_t phys_size[2]); + +typedef bool (*uc_tb_exec_frame_publish_t)(struct uc_struct *uc, + uintptr_t retaddr); + // Invalidate the TB at given address typedef void (*uc_invalidate_tb_t)(struct uc_struct *uc, uint64_t start, size_t len); @@ -158,9 +235,12 @@ typedef uc_tcg_flush_tlb uc_tb_flush_t; typedef uc_err (*uc_set_tlb_t)(struct uc_struct *uc, int mode); // PAuth sign and strip -typedef uc_err (*uc_pauth_sign_t)(struct uc_struct *uc, uint64_t ptr, int key, uint64_t diversifier, uint64_t *signed_ptr); -typedef uc_err (*uc_pauth_strip_t)(struct uc_struct *uc, uint64_t ptr, int key, uint64_t *stripped_ptr); -typedef uc_err (*uc_pauth_auth_t)(struct uc_struct *uc, uint64_t ptr, int key, uint64_t diversifier, bool *valid); +typedef uc_err (*uc_pauth_sign_t)(struct uc_struct *uc, uint64_t ptr, int key, + uint64_t diversifier, uint64_t *signed_ptr); +typedef uc_err (*uc_pauth_strip_t)(struct uc_struct *uc, uint64_t ptr, int key, + uint64_t *stripped_ptr); +typedef uc_err (*uc_pauth_auth_t)(struct uc_struct *uc, uint64_t ptr, int key, + uint64_t diversifier, bool *valid); struct hook { int type; // UC_HOOK_* @@ -190,6 +270,10 @@ typedef size_t (*uc_context_size_t)(struct uc_struct *uc); // Generate a CPU context typedef uc_err (*uc_context_save_t)(struct uc_struct *uc, uc_context *context); +// Validate a CPU context before any restore mutation +typedef uc_err (*uc_context_validate_t)(struct uc_struct *uc, + const uc_context *context); + // Restore a CPU context typedef uc_err (*uc_context_restore_t)(struct uc_struct *uc, uc_context *context); @@ -250,13 +334,14 @@ typedef enum uc_hook_idx { // if statement to check hook bounds #define HOOK_BOUND_CHECK(hh, addr) \ - ((((addr) >= (hh)->begin && (addr) <= (hh)->end) || \ - (hh)->begin > (hh)->end) && \ - !((hh)->to_delete)) + (!((hh)->to_delete) && ((hh)->begin > (hh)->end || \ + ((addr) >= (hh)->begin && (addr) <= (hh)->end))) #define HOOK_EXISTS(uc, idx) ((uc)->hook[idx##_IDX].head != NULL) #define HOOK_EXISTS_BOUNDED(uc, idx, addr) \ _hook_exists_bounded((uc)->hook[idx##_IDX].head, addr) +#define HOOK_EXISTS_BOUNDED_RANGE(uc, idx, begin, end) \ + _hook_exists_bounded_range((uc)->hook[idx##_IDX].head, begin, end) static inline bool _hook_exists_bounded(struct list_item *cur, uint64_t addr) { @@ -268,12 +353,35 @@ static inline bool _hook_exists_bounded(struct list_item *cur, uint64_t addr) return false; } +static inline bool _hook_exists_bounded_range(struct list_item *cur, + uint64_t begin, uint64_t end) +{ + while (cur != NULL) { + struct hook *hook = (struct hook *)cur->data; + + if (!hook->to_delete && (hook->begin > hook->end || + (hook->begin <= end && hook->end >= begin))) { + return true; + } + cur = cur->next; + } + return false; +} + // relloc increment, KEEP THIS A POWER OF 2! #define MEM_BLOCK_INCR 32 typedef struct TargetPageBits TargetPageBits; typedef struct TCGContext TCGContext; +#define UC_HOOK_DISPATCH_CACHE_SIZE 8 + +typedef struct HookDispatchCacheEntry { + uint64_t address; + uintptr_t generation; + struct list_item *first_match; +} HookDispatchCacheEntry; + struct uc_struct { uc_arch arch; uc_mode mode; @@ -305,17 +413,26 @@ struct uc_struct { uc_memory_mapping_t memory_mapping; uc_memory_filter_t memory_filter_subregions; uc_flatview_copy_t flatview_copy; + uc_flatview_reserve_t flatview_reserve; + uc_address_space_restore_flatview_t address_space_restore_flatview; uc_mem_unmap_t memory_unmap; - uc_mem_unmap_t memory_moveout; - uc_mem_unmap_t memory_movein; + uc_memory_move_t memory_moveout; + uc_memory_move_t memory_movein; + uc_memory_restore_topology_t memory_restore_topology; + uc_memory_mapping_free_t memory_mapping_free; + uc_memory_mapping_prune_t memory_mapping_prune; + uc_memory_mapping_normalize_t memory_mapping_normalize; uc_readonly_mem_t readonly_mem; uc_cpus_init cpus_init; uc_target_page_init target_page; uc_softfloat_initialize softfloat_initialize; uc_tcg_flush_tlb tcg_flush_tlb; + uc_tb_exec_frame_resolve_t tb_exec_frame_resolve; + uc_tb_exec_frame_publish_t tb_exec_frame_publish; uc_invalidate_tb_t uc_invalidate_tb; uc_gen_tb_t uc_gen_tb; uc_tb_flush_t tb_flush; + uc_tb_flush_t tb_flush_deferred; uc_add_inline_hook_t add_inline_hook; uc_del_inline_hook_t del_inline_hook; uc_pauth_sign_t pauth_sign; @@ -324,6 +441,7 @@ struct uc_struct { uc_context_size_t context_size; uc_context_save_t context_save; + uc_context_validate_t context_validate; uc_context_restore_t context_restore; /* only 1 cpu in unicorn, @@ -365,11 +483,15 @@ struct uc_struct { bool memory_region_update_pending; uc_set_tlb_t set_tlb; + uc_tlb_type tlb_mode; // linked lists containing hooks per type struct list hook[UC_HOOK_MAX]; struct list hooks_to_del; int hooks_count[UC_HOOK_MAX]; + uintptr_t hook_generation[UC_HOOK_MAX]; + HookDispatchCacheEntry hook_dispatch_cache[UC_HOOK_MAX] + [UC_HOOK_DISPATCH_CACHE_SIZE]; // hook to count number of instructions for uc_emu_start() uc_hook count_hook; @@ -380,15 +502,15 @@ struct uc_struct { int size_recur_mem; // size for mem access when in a recursive call bool init_tcg; // already initialized local TCGv variables? - bool stop_request; // request to immediately stop emulation - for - // uc_emu_stop() + int stop_request; // request to immediately stop emulation - for + // uc_emu_stop(); accessed atomically across threads bool quit_request; // request to quit the current TB, but continue to // emulate - for uc_mem_protect() bool emulation_done; // emulation is done by uc_emu_start() - bool timed_out; // emulation timed out, that can retrieve via - // uc_query(UC_QUERY_TIMEOUT) - QemuThread timer; // timer for emulation timeout + int timed_out; // emulation timed out, that can retrieve via + // uc_query(UC_QUERY_TIMEOUT); accessed atomically uint64_t timeout; // timeout for uc_emu_start() + UcEmuFrame emu_frames[UC_MAX_NESTED_LEVEL]; uint64_t invalid_addr; // invalid address to be accessed int invalid_error; // invalid memory code: 1 = READ, 2 = WRITE, 3 = CODE @@ -400,8 +522,12 @@ struct uc_struct { // details. int thumb; // thumb mode for ARM - MemoryRegion **mapped_blocks; + UcMapping **mapped_blocks; uint32_t mapped_block_count; + uint32_t mapped_block_capacity; + UcMapping *mapping_records; + uc_context *memory_contexts; + uint32_t memory_context_count; uint32_t mapped_block_cache_index; void *qemu_thread_data; // to support cross compile to Windows // (qemu-thread-win32.c) @@ -422,10 +548,11 @@ struct uc_struct { // workaround to treat the IT block as a whole block. bool init_done; // Whether the initialization is done. - sigjmp_buf jmp_bufs[UC_MAX_NESTED_LEVEL]; // To support nested uc_emu_start - int nested_level; // Current nested_level + sigjmp_buf jmp_bufs[UC_MAX_NESTED_JMP_LEVEL]; + int nested_level; // Current nested_level - struct TranslationBlock *last_tb; // The real last tb we executed. + uc_tb last_tb; + bool last_tb_valid; FlatView *empty_view; // Static function variable moved from flatviews_init @@ -433,21 +560,69 @@ struct uc_struct { #if defined(WIN32) && defined(WIN32_ENABLE_VEH) bool prealloc; // Commit the whole code gen buffer upfront instead of // relying on lazy commit via the vectored exception handler. - // Avoids installing the process-global VEH, which is required - // for safe concurrent use of multiple uc instances. Only - // meaningful when the VEH path is compiled in; without it the - // buffer is always committed upfront. + // Avoids installing the process-global VEH, which is + // required for safe concurrent use of multiple uc instances. + // Only meaningful when the VEH path is compiled in; without + // it the buffer is always committed upfront. PVOID seh_handle; + PVOID vch_handle; void *seh_closure; #endif - GArray *unmapped_regions; int32_t snapshot_level; uint64_t nested; // the nested level of all exposed API bool thread_executable_entry; bool current_executable; bool skip_sync_pc_on_exit; + bool tb_flush_pending; + unsigned int tb_exec_depth; + UcTbExecFrame *active_tb_exec_frame; + UcTbExecFrame tb_exec_frames[UC_MAX_NESTED_JMP_LEVEL]; + UcTestAllocFailSite test_alloc_fail_site; }; +static inline bool uc_uses_tcg_count(const struct uc_struct *uc) +{ + return uc->emu_count != 0 && uc->arch != UC_ARCH_ARM && + uc->arch != UC_ARCH_M68K && uc->arch != UC_ARCH_MIPS && + uc->arch != UC_ARCH_SPARC; +} + +static inline void hook_dispatch_cache_invalidate(struct uc_struct *uc, + unsigned int index) +{ + uc->hook_generation[index]++; + if (uc->hook_generation[index] == 0) { + memset(uc->hook_dispatch_cache[index], 0, + sizeof(uc->hook_dispatch_cache[index])); + uc->hook_generation[index] = 1; + } +} + +static inline struct list_item *hook_dispatch_first_match(struct uc_struct *uc, + unsigned int index, + uint64_t address) +{ + unsigned int cache_index = + (address >> 3) & (UC_HOOK_DISPATCH_CACHE_SIZE - 1); + HookDispatchCacheEntry *entry = + &uc->hook_dispatch_cache[index][cache_index]; + struct list_item *cur; + + if (entry->generation == uc->hook_generation[index] && + entry->address == address) { + return entry->first_match; + } + for (cur = uc->hook[index].head; cur != NULL; cur = cur->next) { + if (HOOK_BOUND_CHECK((struct hook *)cur->data, address)) { + break; + } + } + entry->address = address; + entry->generation = uc->hook_generation[index]; + entry->first_match = cur; + return cur; +} + // Metadata stub for the variable-size cpu context used with uc_context_*() struct uc_context { size_t context_size; // size of the real internal context structure @@ -457,7 +632,14 @@ struct uc_context { bool ramblock_freed; // wheter there was a some ramblock freed RAMBlock *last_block; // The last element of the ramblock list FlatView *fv; // The current flatview of the memory - char data[0]; // context + struct uc_struct *memory_owner; + struct uc_context *memory_prev; + struct uc_context *memory_next; + UcContextMapping *mappings; + MemoryRegion **memory_regions; + uint32_t mapping_count; + uint32_t memory_region_count; + char data[0]; // context }; // We have to support 32bit system so we can't hold uint64_t on void* @@ -475,7 +657,7 @@ static inline int uc_addr_is_exit(uc_engine *uc, uint64_t addr) if (uc->use_exits) { return g_tree_lookup(uc->ctl_exits, (gpointer)(&addr)) == (gpointer)1; } else { - return uc->exits[uc->nested_level - 1] == addr; + return uc->nested_level != 0 && uc->exits[uc->nested_level - 1] == addr; } } @@ -515,12 +697,38 @@ static inline void hooked_regions_add(struct hook *h, uint64_t start, } } -static inline void hooked_regions_check_single(struct list_item *cur, - uint64_t start, uint64_t length) +static inline void hooked_regions_check_code(struct list_item *cur, + uint64_t start, uint64_t length) { + uint64_t end; + + if (length == 0) { + end = start; + } else { + end = start + length - 1; + if (end < start) { + end = UINT64_MAX; + } + } while (cur != NULL) { - if (HOOK_BOUND_CHECK((struct hook *)cur->data, start)) { - hooked_regions_add((struct hook *)cur->data, start, length); + struct hook *hook = (struct hook *)cur->data; + + if (!hook->to_delete && (hook->begin > hook->end || + (hook->begin <= end && hook->end >= start))) { + hooked_regions_add(hook, start, length); + } + cur = cur->next; + } +} + +static inline void hooked_regions_check_block(struct list_item *cur, + uint64_t start, uint64_t length) +{ + while (cur != NULL) { + struct hook *hook = (struct hook *)cur->data; + + if (HOOK_BOUND_CHECK(hook, start)) { + hooked_regions_add(hook, start, length); } cur = cur->next; } @@ -529,10 +737,10 @@ static inline void hooked_regions_check_single(struct list_item *cur, static inline void hooked_regions_check(uc_engine *uc, uint64_t start, uint64_t length) { - // Only UC_HOOK_BLOCK and UC_HOOK_CODE might be wrongle cached! - hooked_regions_check_single(uc->hook[UC_HOOK_CODE_IDX].head, start, length); - hooked_regions_check_single(uc->hook[UC_HOOK_BLOCK_IDX].head, start, - length); + hooked_regions_check_code(uc->hook[UC_HOOK_CODE_IDX].head, start, length); + hooked_regions_check_code(uc->hook[UC_HOOK_MEM_FETCH_IDX].head, start, + length); + hooked_regions_check_block(uc->hook[UC_HOOK_BLOCK_IDX].head, start, length); } /* @@ -557,12 +765,20 @@ static inline uc_err break_translation_loop(uc_engine *uc) return UC_ERR_OK; } +static inline bool uc_stop_requested(uc_engine *uc) +{ + return qatomic_read(&uc->stop_request) != 0; +} + +static inline void uc_set_stop_request(uc_engine *uc, bool stop) +{ + qatomic_set(&uc->stop_request, stop); +} + static inline void revert_uc_emu_stop(uc_engine *uc) { - uc->stop_request = 0; - uc->cpu->exit_request = 0; - uc->cpu->tcg_exit_req = 0; - uc->cpu->icount_decr_ptr->u16.high = 0; + uc_set_stop_request(uc, false); + qatomic_set(&uc->cpu->icount_decr_ptr->u16.high, 0); } #ifdef UNICORN_TRACER diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index 5b00ad269f..ecacc887c2 100644 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -43,12 +43,14 @@ typedef size_t uc_hook; #define DEFAULT_VISIBILITY #endif +#if defined(_MSC_VER) || defined(__MINGW32__) #ifdef _MSC_VER #pragma warning(disable : 4201) #pragma warning(disable : 4100) +#endif #ifdef UNICORN_SHARED #define UNICORN_EXPORT __declspec(dllexport) -#else // defined(UNICORN_STATIC) +#else #define UNICORN_EXPORT #endif #else diff --git a/qemu/aarch64.h b/qemu/aarch64.h index 41e39d3ff1..dbb54d78d1 100644 --- a/qemu/aarch64.h +++ b/qemu/aarch64.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _aarch64 #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_aarch64 #define gen_helper_check_exit_request gen_helper_check_exit_request_aarch64 +#define gen_helper_exit_request gen_helper_exit_request_aarch64 +#define helper_check_counted_entry helper_check_counted_entry_aarch64 +#define helper_check_exit_request helper_check_exit_request_aarch64 +#define helper_exit_request helper_exit_request_aarch64 +#define helper_uc_tracecode_single helper_uc_tracecode_single_aarch64 +#define cpu_restore_icount cpu_restore_icount_aarch64 +#define cpu_tcg_exit_request cpu_tcg_exit_request_aarch64 #define unicorn_fill_tlb unicorn_fill_tlb_aarch64 #define reg_read reg_read_aarch64 #define reg_write reg_write_aarch64 @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_aarch64 #define flatview_translate flatview_translate_aarch64 #define flatview_copy flatview_copy_aarch64 +#define flatview_reserve flatview_reserve_aarch64 +#define address_space_restore_flatview address_space_restore_flatview_aarch64 #define address_space_translate_for_iotlb address_space_translate_for_iotlb_aarch64 #define qemu_get_cpu qemu_get_cpu_aarch64 #define cpu_address_space_init cpu_address_space_init_aarch64 @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_aarch64 #define memory_moveout memory_moveout_aarch64 #define memory_movein memory_movein_aarch64 +#define memory_mapping_restore_topology memory_mapping_restore_topology_aarch64 +#define memory_mapping_free memory_mapping_free_aarch64 +#define memory_mapping_prune memory_mapping_prune_aarch64 +#define memory_mapping_normalize memory_mapping_normalize_aarch64 #define memory_free memory_free_aarch64 #define flatview_unref flatview_unref_aarch64 #define address_space_get_flatview address_space_get_flatview_aarch64 @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_aarch64 #define tb_cleanup tb_cleanup_aarch64 #define tb_flush tb_flush_aarch64 +#define tb_flush_jit tb_flush_jit_aarch64 #define tb_phys_invalidate tb_phys_invalidate_aarch64 #define tb_gen_code tb_gen_code_aarch64 #define tb_reset_jump tb_reset_jump_aarch64 diff --git a/qemu/accel/tcg/cpu-exec-common.c b/qemu/accel/tcg/cpu-exec-common.c index f9f50dd144..fce4005075 100644 --- a/qemu/accel/tcg/cpu-exec-common.c +++ b/qemu/accel/tcg/cpu-exec-common.c @@ -35,6 +35,24 @@ void cpu_reloading_memory_map(void) void cpu_loop_exit(CPUState *cpu) { + unsigned int level = cpu->uc->nested_level - 1; + UcTbExecFrame *frame = &cpu->uc->tb_exec_frames[level]; + + if (frame->active) { + frame->active = false; + frame->exit_requested = false; + frame->tb = NULL; + g_assert(cpu->uc->tb_exec_depth != 0); + cpu->uc->tb_exec_depth--; + } + cpu->uc->active_tb_exec_frame = NULL; + while (level != 0) { + frame = &cpu->uc->tb_exec_frames[--level]; + if (frame->active) { + cpu->uc->active_tb_exec_frame = frame; + break; + } + } /* Unlock JIT write protect if applicable. */ if (cpu->uc->nested_level == 1) { tb_exec_unlock(cpu->uc); diff --git a/qemu/accel/tcg/cpu-exec.c b/qemu/accel/tcg/cpu-exec.c index f3d8bbabfe..4bde759143 100644 --- a/qemu/accel/tcg/cpu-exec.c +++ b/qemu/accel/tcg/cpu-exec.c @@ -29,6 +29,7 @@ #include "hw/core/tcg-cpu-ops.h" #include "sysemu/cpus.h" #include "uc_priv.h" +#include "tb-exec-frame.h" /* -icount align implementation. */ @@ -47,6 +48,17 @@ typedef struct SyncClocks { #define MAX_DELAY_PRINT_RATE 2000000000LL #define MAX_NB_PRINTS 100 +#if defined(__clang__) && __has_attribute(no_sanitize) +static inline __attribute__((always_inline, no_sanitize("function"))) +uintptr_t tcg_qemu_tb_exec_no_sanitize(CPUArchState *env, uint8_t *tb_ptr) +{ + return tcg_qemu_tb_exec(env, tb_ptr); +} +#else +#define tcg_qemu_tb_exec_no_sanitize(env, tb_ptr) \ + tcg_qemu_tb_exec(env, tb_ptr) +#endif + /* Execute a TB, and fix up the CPU state afterwards if necessary */ static inline tcg_target_ulong cpu_tb_exec(CPUState *cpu, TranslationBlock *itb) { @@ -55,10 +67,24 @@ static inline tcg_target_ulong cpu_tb_exec(CPUState *cpu, TranslationBlock *itb) TranslationBlock *last_tb; int tb_exit; uint8_t *tb_ptr = itb->tc.ptr; + unsigned int level = cpu->uc->nested_level - 1; + UcTbExecFrame *frame = &cpu->uc->tb_exec_frames[level]; + UcTbExecFrame *previous_frame = cpu->uc->active_tb_exec_frame; UC_TRACE_START(UC_TRACE_TB_EXEC); tb_exec_lock(cpu->uc); - ret = tcg_qemu_tb_exec(env, tb_ptr); + g_assert(!frame->active); + tb_exec_frame_set_tb(frame, itb); + frame->exit_requested = false; + frame->active = true; + cpu->uc->active_tb_exec_frame = frame; + cpu->uc->tb_exec_depth++; + ret = tcg_qemu_tb_exec_no_sanitize(env, tb_ptr); + cpu->uc->active_tb_exec_frame = previous_frame; + frame->active = false; + frame->exit_requested = false; + tb_exec_frame_clear(frame); + cpu->uc->tb_exec_depth--; if (cpu->uc->nested_level == 1) { // Only unlock (allow writing to JIT area) if we are the outmost uc_emu_start tb_exec_unlock(cpu->uc); @@ -96,7 +122,6 @@ static inline tcg_target_ulong cpu_tb_exec(CPUState *cpu, TranslationBlock *itb) } } - cpu->tcg_exit_req = 0; } return ret; } @@ -107,7 +132,7 @@ static void cpu_exec_nocache(CPUState *cpu, int max_cycles, TranslationBlock *orig_tb, bool ignore_icount) { TranslationBlock *tb; - uint32_t cflags = curr_cflags() | CF_NOCACHE; + uint32_t cflags = curr_cflags(cpu->uc) | CF_NOCACHE; if (ignore_icount) { cflags &= ~CF_USE_ICOUNT; @@ -252,6 +277,9 @@ static inline TranslationBlock *tb_find(CPUState *cpu, uc_engine *uc = cpu->uc; struct list_item *cur; struct hook *hook; + bool notify_edge = true; + + retry: tb = tb_lookup__cpu_state(cpu, &pc, &cs_base, &flags, cf_mask); if (tb == NULL) { @@ -261,9 +289,12 @@ static inline TranslationBlock *tb_find(CPUState *cpu, /* We add the TB in the virtual pc hash table for the fast lookup */ cpu->tb_jmp_cache[tb_jmp_cache_hash_func(cpu->uc, pc)] = tb; - if (uc->last_tb) { + if (uc->last_tb_valid && notify_edge) { + unsigned int flush_count = + uc->tcg_ctx->tb_ctx.tb_flush_count; + UC_TB_COPY(&cur_tb, tb); - UC_TB_COPY(&prev_tb, uc->last_tb); + prev_tb = uc->last_tb; for (cur = uc->hook[UC_HOOK_EDGE_GENERATED_IDX].head; cur != NULL && (hook = (struct hook *)cur->data); cur = cur->next) { if (hook->to_delete) { @@ -274,6 +305,19 @@ static inline TranslationBlock *tb_find(CPUState *cpu, JIT_CALLBACK_GUARD(((uc_hook_edge_gen_t)hook->callback)(uc, &cur_tb, &prev_tb, hook->user_data)); } } + notify_edge = false; + if (unlikely(qatomic_read(&cpu->exit_request_pending))) { + return NULL; + } + if (unlikely(flush_count != + uc->tcg_ctx->tb_ctx.tb_flush_count)) { + last_tb = NULL; + goto retry; + } + if (unlikely(tb_cflags(tb) & CF_INVALID)) { + last_tb = NULL; + goto retry; + } } } /* We don't take care of direct jumps when address mapping changes in @@ -435,15 +479,18 @@ static inline bool cpu_handle_exception(CPUState *cpu, int *ret) return false; } +static inline bool cpu_acknowledge_pending_exit(CPUState *cpu) +{ + return qatomic_xchg_acquire(&cpu->exit_request_pending, 0) != 0; +} + static inline bool cpu_handle_interrupt(CPUState *cpu, TranslationBlock **last_tb) { /* Clear the interrupt flag now since we're processing - * cpu->interrupt_request and cpu->exit_request. - * Ensure zeroing happens before reading cpu->exit_request or - * cpu->interrupt_request (see also smp_wmb in cpu_exit()) + * cpu->interrupt_request and the pending exit edge. */ - cpu_neg(cpu)->icount_decr.u16.high = 0; + qatomic_set(&cpu_neg(cpu)->icount_decr.u16.high, 0); if (unlikely(cpu->interrupt_request)) { int interrupt_request; @@ -497,8 +544,7 @@ static inline bool cpu_handle_interrupt(CPUState *cpu, } /* Finally, check if we need to exit to the main loop. */ - if (unlikely(cpu->exit_request)) { - cpu->exit_request = 0; + if (unlikely(cpu_acknowledge_pending_exit(cpu))) { if (cpu->exception_index == -1) { cpu->exception_index = EXCP_INTERRUPT; } @@ -513,12 +559,38 @@ static inline void cpu_loop_exec_tb(CPUState *cpu, TranslationBlock *tb, { uintptr_t ret; int32_t insns_left; + TranslationBlock *executed_tb = tb; + TranslationBlock *snapshot_tb; + uc_tb executed_snapshot; + bool track_edge = HOOK_EXISTS(cpu->uc, UC_HOOK_EDGE_GENERATED); + unsigned int flush_count = cpu->uc->tcg_ctx->tb_ctx.tb_flush_count; + + if (track_edge) { + UC_TB_COPY(&executed_snapshot, executed_tb); + } // trace_exec_tb(tb, tb->pc); ret = cpu_tb_exec(cpu, tb); - cpu->uc->last_tb = tb; // Trace the last tb we executed. tb = (TranslationBlock *)(ret & ~TB_EXIT_MASK); + snapshot_tb = tb != NULL ? tb : executed_tb; + if (track_edge) { + if (tb != NULL && + flush_count == cpu->uc->tcg_ctx->tb_ctx.tb_flush_count) { + UC_TB_COPY(&cpu->uc->last_tb, snapshot_tb); + } else { + cpu->uc->last_tb = executed_snapshot; + } + cpu->uc->last_tb_valid = true; + } else { + cpu->uc->last_tb_valid = false; + } *tb_exit = ret & TB_EXIT_MASK; + if (cpu->uc->tb_flush_pending && cpu->uc->tb_exec_depth == 0) { + cpu->uc->tb_flush_pending = false; + cpu->uc->tb_flush(cpu->uc); + *last_tb = NULL; + return; + } if (*tb_exit != TB_EXIT_REQUESTED) { *last_tb = tb; return; @@ -560,6 +632,7 @@ int cpu_exec(struct uc_struct *uc, CPUState *cpu) // SyncClocks sc = { 0 }; if (cpu_handle_halt(cpu)) { + cpu_acknowledge_pending_exit(cpu); return EXCP_HALTED; } @@ -591,6 +664,10 @@ int cpu_exec(struct uc_struct *uc, CPUState *cpu) #endif /* buggy compiler */ assert_no_pages_locked(); + if (uc->tb_flush_pending && uc->tb_exec_depth == 0) { + uc->tb_flush_pending = false; + uc->tb_flush(uc); + } } /* if an exception is pending, we execute it here */ @@ -608,13 +685,13 @@ int cpu_exec(struct uc_struct *uc, CPUState *cpu) have CF_INVALID set, -1 is a convenient invalid value that does not require tcg headers for cpu_common_reset. */ if (cflags == -1) { - cflags = curr_cflags(); + cflags = curr_cflags(uc); } else { cpu->cflags_next_tb = -1; } tb = tb_find(cpu, last_tb, tb_exit, cflags); - if (unlikely(cpu->exit_request)) { + if (unlikely(qatomic_read(&cpu->exit_request_pending))) { continue; } cpu_loop_exec_tb(cpu, tb, &last_tb, &tb_exit); @@ -624,8 +701,7 @@ int cpu_exec(struct uc_struct *uc, CPUState *cpu) } } - // Unicorn: Clear any TCG exit flag that might have been left set by exit requests - uc->cpu->tcg_exit_req = 0; + cpu_acknowledge_pending_exit(cpu); cpu_tcg_exec_exit(cpu); // rcu_read_unlock(); diff --git a/qemu/accel/tcg/cputlb.c b/qemu/accel/tcg/cputlb.c index dd5d7c7479..f61202998c 100644 --- a/qemu/accel/tcg/cputlb.c +++ b/qemu/accel/tcg/cputlb.c @@ -43,22 +43,23 @@ /* #define DEBUG_TLB_LOG */ #ifdef DEBUG_TLB -# define DEBUG_TLB_GATE 1 -# ifdef DEBUG_TLB_LOG -# define DEBUG_TLB_LOG_GATE 1 -# else -# define DEBUG_TLB_LOG_GATE 0 -# endif +#define DEBUG_TLB_GATE 1 +#ifdef DEBUG_TLB_LOG +#define DEBUG_TLB_LOG_GATE 1 #else -# define DEBUG_TLB_GATE 0 -# define DEBUG_TLB_LOG_GATE 0 +#define DEBUG_TLB_LOG_GATE 0 +#endif +#else +#define DEBUG_TLB_GATE 0 +#define DEBUG_TLB_LOG_GATE 0 #endif #if 0 -#define assert_cpu_is_self(cpu) do { \ - if (DEBUG_TLB_GATE) { \ - g_assert(!(cpu)->created || qemu_cpu_is_self(cpu)); \ - } \ +#define assert_cpu_is_self(cpu) \ + do { \ + if (DEBUG_TLB_GATE) { \ + g_assert(!(cpu)->created || qemu_cpu_is_self(cpu)); \ + } \ } while (0) #endif @@ -70,6 +71,7 @@ QEMU_BUILD_BUG_ON(sizeof(target_ulong) > sizeof(run_on_cpu_data)); */ QEMU_BUILD_BUG_ON(NB_MMU_MODES > 16); #define ALL_MMUIDX_BITS ((1 << NB_MMU_MODES) - 1) +#define TLB_VIRTUAL_COLLISION_HOLD_NS (INT64_C(5) * 1000 * 1000 * 1000) static inline size_t tlb_n_entries(CPUTLBDescFast *fast) { @@ -81,8 +83,12 @@ static inline size_t sizeof_tlb(CPUTLBDescFast *fast) return fast->mask + (1 << CPU_TLB_ENTRY_BITS); } -static void tlb_window_reset(CPUTLBDesc *desc, int64_t ns, - size_t max_entries) +static inline void tlb_mutated(CPUArchState *env) +{ + env_tlb(env)->c.mutation_epoch++; +} + +static void tlb_window_reset(CPUTLBDesc *desc, int64_t ns, size_t max_entries) { desc->window_begin_ns = ns; desc->window_max_entries = max_entries; @@ -128,8 +134,9 @@ static void tlb_window_reset(CPUTLBDesc *desc, int64_t ns, * high), since otherwise we are likely to have a significant amount of * conflict misses. */ -static void tlb_mmu_resize_locked(struct uc_struct *uc, CPUTLBDesc *desc, CPUTLBDescFast *fast, - int64_t now) +static void tlb_mmu_resize_locked(struct uc_struct *uc, CPUTLBDesc *desc, + CPUTLBDescFast *fast, int64_t now, + size_t minimum_size) { size_t old_size = tlb_n_entries(fast); size_t rate; @@ -164,6 +171,16 @@ static void tlb_mmu_resize_locked(struct uc_struct *uc, CPUTLBDesc *desc, CPUTLB } new_size = MAX(ceil, 1 << CPU_TLB_DYN_MIN_BITS); } + new_size = MAX(new_size, minimum_size); + new_size = MIN(new_size, 1ULL << CPU_TLB_DYN_MAX_BITS); + if (desc->collision_floor != 0) { + if (now < desc->collision_floor_expires_ns) { + new_size = MAX(new_size, desc->collision_floor); + } else { + desc->collision_floor = 0; + desc->collision_floor_expires_ns = 0; + } + } if (new_size == old_size) { if (window_expired) { @@ -194,7 +211,7 @@ static void tlb_mmu_resize_locked(struct uc_struct *uc, CPUTLBDesc *desc, CPUTLB while (fast->table == NULL || desc->fulltlb == NULL) { if (new_size == (1 << CPU_TLB_DYN_MIN_BITS)) { fprintf(stderr, "%s: %s.\n", __func__, strerror(errno)); - abort(); // FIXME: do not abort + abort(); // FIXME: do not abort } new_size = MAX(new_size >> 1, 1 << CPU_TLB_DYN_MIN_BITS); fast->mask = (new_size - 1) << CPU_TLB_ENTRY_BITS; @@ -212,6 +229,8 @@ static void tlb_mmu_flush_locked(CPUTLBDesc *desc, CPUTLBDescFast *fast) desc->large_page_addr = -1; desc->large_page_mask = -1; desc->vindex = 0; + desc->collision_misses = 0; + desc->collision_min_size = 0; memset(fast->table, -1, sizeof_tlb(fast)); memset(desc->vtable, -1, sizeof(desc->vtable)); } @@ -222,8 +241,10 @@ static void tlb_flush_one_mmuidx_locked(CPUArchState *env, int mmu_idx, CPUTLBDesc *desc = &env_tlb(env)->d[mmu_idx]; CPUTLBDescFast *fast = &env_tlb(env)->f[mmu_idx]; - tlb_mmu_resize_locked(env->uc, desc, fast, now); + tlb_mmu_resize_locked(env->uc, desc, fast, now, 0); tlb_mmu_flush_locked(desc, fast); + env_tlb(env)->c.table_epoch++; + tlb_mutated(env); } static void tlb_mmu_init(CPUTLBDesc *desc, CPUTLBDescFast *fast, int64_t now) @@ -232,6 +253,10 @@ static void tlb_mmu_init(CPUTLBDesc *desc, CPUTLBDescFast *fast, int64_t now) tlb_window_reset(desc, now, 0); desc->n_used_entries = 0; + desc->collision_misses = 0; + desc->collision_min_size = 0; + desc->collision_floor = 0; + desc->collision_floor_expires_ns = 0; fast->mask = (n_entries - 1) << CPU_TLB_ENTRY_BITS; fast->table = g_new(CPUTLBEntry, n_entries); desc->fulltlb = g_new0(CPUTLBEntryFull, n_entries); @@ -256,6 +281,8 @@ void tlb_init(CPUState *cpu) /* All tlbs are initialized flushed. */ env_tlb(env)->c.dirty = 0; + env_tlb(env)->c.mutation_epoch = 0; + env_tlb(env)->c.table_epoch = 0; for (i = 0; i < NB_MMU_MODES; i++) { tlb_mmu_init(&env_tlb(env)->d[i], &env_tlb(env)->f[i], now); @@ -306,19 +333,21 @@ static void tlb_flush_by_mmuidx_async_work(CPUState *cpu, run_on_cpu_data data) if (to_clean == ALL_MMUIDX_BITS) { env_tlb(env)->c.full_flush_count = env_tlb(env)->c.full_flush_count + 1; } else { - env_tlb(env)->c.part_flush_count = env_tlb(env)->c.part_flush_count + ctpop16(to_clean); + env_tlb(env)->c.part_flush_count = + env_tlb(env)->c.part_flush_count + ctpop16(to_clean); if (to_clean != asked) { - env_tlb(env)->c.elide_flush_count = env_tlb(env)->c.elide_flush_count + ctpop16(asked & ~to_clean); + env_tlb(env)->c.elide_flush_count = + env_tlb(env)->c.elide_flush_count + ctpop16(asked & ~to_clean); } } } void tlb_flush_by_mmuidx(CPUState *cpu, uint16_t idxmap) { - //if (cpu->created && !qemu_cpu_is_self(cpu)) { - // tlb_flush_by_mmuidx_async_work(cpu, RUN_ON_CPU_HOST_INT(idxmap)); - //} else { - tlb_flush_by_mmuidx_async_work(cpu, RUN_ON_CPU_HOST_INT(idxmap)); + // if (cpu->created && !qemu_cpu_is_self(cpu)) { + // tlb_flush_by_mmuidx_async_work(cpu, RUN_ON_CPU_HOST_INT(idxmap)); + // } else { + tlb_flush_by_mmuidx_async_work(cpu, RUN_ON_CPU_HOST_INT(idxmap)); //} } @@ -353,7 +382,8 @@ void tlb_flush_all_cpus_synced(CPUState *src_cpu) tlb_flush_by_mmuidx_all_cpus_synced(src_cpu, ALL_MMUIDX_BITS); } -static inline bool tlb_hit_page_anyprot(struct uc_struct *uc, CPUTLBEntry *tlb_entry, +static inline bool tlb_hit_page_anyprot(struct uc_struct *uc, + CPUTLBEntry *tlb_entry, target_ulong page) { return tlb_hit_page(uc, tlb_entry->addr_read, page) || @@ -361,6 +391,26 @@ static inline bool tlb_hit_page_anyprot(struct uc_struct *uc, CPUTLBEntry *tlb_e tlb_hit_page(uc, tlb_entry->addr_code, page); } +static bool tlb_entry_page(struct uc_struct *uc, const CPUTLBEntry *entry, + target_ulong *page) +{ + target_ulong addr; + + (void)uc; + addr = entry->addr_read; + if (addr == (target_ulong)-1) { + addr = tlb_addr_write(entry); + } + if (addr == (target_ulong)-1) { + addr = entry->addr_code; + } + if (addr == (target_ulong)-1) { + return false; + } + *page = addr & TARGET_PAGE_MASK; + return true; +} + /** * tlb_entry_is_empty - return true if the entry is not in use * @te: pointer to CPUTLBEntry @@ -371,7 +421,8 @@ static inline bool tlb_entry_is_empty(const CPUTLBEntry *te) } /* Called with tlb_c.lock held */ -static inline bool tlb_flush_entry_locked(struct uc_struct *uc, CPUTLBEntry *tlb_entry, +static inline bool tlb_flush_entry_locked(struct uc_struct *uc, + CPUTLBEntry *tlb_entry, target_ulong page) { if (tlb_hit_page_anyprot(uc, tlb_entry, page)) { @@ -410,6 +461,7 @@ static void tlb_flush_page_locked(CPUArchState *env, int midx, tlb_n_used_entries_dec(env, midx); } tlb_flush_vtlb_page_locked(env, midx, page); + tlb_mutated(env); } } @@ -422,8 +474,7 @@ static void tlb_flush_page_locked(CPUArchState *env, int midx, * Helper for tlb_flush_page_by_mmuidx and friends, flush one page * at @addr from the tlbs indicated by @idxmap from @cpu. */ -static void tlb_flush_page_by_mmuidx_async_0(CPUState *cpu, - target_ulong addr, +static void tlb_flush_page_by_mmuidx_async_0(CPUState *cpu, target_ulong addr, uint16_t idxmap) { CPUArchState *env = cpu->env_ptr; @@ -454,7 +505,7 @@ static void tlb_flush_page_by_mmuidx_async_1(CPUState *cpu, #ifdef TARGET_ARM struct uc_struct *uc = cpu->uc; #endif - target_ulong addr_and_idxmap = (target_ulong) data.target_ptr; + target_ulong addr_and_idxmap = (target_ulong)data.target_ptr; target_ulong addr = addr_and_idxmap & TARGET_PAGE_MASK; uint16_t idxmap = addr_and_idxmap & ~TARGET_PAGE_MASK; @@ -495,7 +546,7 @@ void tlb_flush_page_by_mmuidx(CPUState *cpu, target_ulong addr, uint16_t idxmap) addr &= TARGET_PAGE_MASK; // if (qemu_cpu_is_self(cpu)) { - tlb_flush_page_by_mmuidx_async_0(cpu, addr, idxmap); + tlb_flush_page_by_mmuidx_async_0(cpu, addr, idxmap); //} #if 0 else if (idxmap < TARGET_PAGE_SIZE) { @@ -582,9 +633,10 @@ void tlb_flush_page_by_mmuidx_all_cpus_synced(CPUState *src_cpu, if (idxmap < TARGET_PAGE_SIZE) { flush_all_helper(src_cpu, tlb_flush_page_by_mmuidx_async_1, RUN_ON_CPU_TARGET_PTR(addr | idxmap)); - tlb_flush_page_by_mmuidx_async_1(src_cpu, RUN_ON_CPU_TARGET_PTR(addr | idxmap)); + tlb_flush_page_by_mmuidx_async_1(src_cpu, + RUN_ON_CPU_TARGET_PTR(addr | idxmap)); } else { - //CPUState *dst_cpu; + // CPUState *dst_cpu; TLBFlushPageByMMUIdxData *d; #if 0 @@ -626,7 +678,6 @@ void tlb_unprotect_code(struct uc_struct *uc, ram_addr_t ram_addr) cpu_physical_memory_set_dirty_flag(ram_addr, DIRTY_MEMORY_CODE); } - /* * Dirty write flag handling * @@ -643,13 +694,14 @@ void tlb_unprotect_code(struct uc_struct *uc, ram_addr_t ram_addr) * * Called with tlb_c.lock held. */ -static void tlb_reset_dirty_range_locked(struct uc_struct *uc, CPUTLBEntry *tlb_entry, +static void tlb_reset_dirty_range_locked(struct uc_struct *uc, + CPUTLBEntry *tlb_entry, uintptr_t start, uintptr_t length) { uintptr_t addr = tlb_entry->addr_write; - if ((addr & (TLB_INVALID_MASK | TLB_MMIO | - TLB_DISCARD_WRITE | TLB_NOTDIRTY)) == 0) { + if ((addr & (TLB_INVALID_MASK | TLB_MMIO | TLB_DISCARD_WRITE | + TLB_NOTDIRTY)) == 0) { addr &= TARGET_PAGE_MASK; addr += tlb_entry->addend; if ((addr - start) < length) { @@ -662,13 +714,15 @@ static void tlb_reset_dirty_range_locked(struct uc_struct *uc, CPUTLBEntry *tlb_ } } -static void tlb_reset_dirty_range_by_vaddr_locked(struct uc_struct *uc, CPUTLBEntry *tlb_entry, - target_ulong start, target_ulong length) +static void tlb_reset_dirty_range_by_vaddr_locked(struct uc_struct *uc, + CPUTLBEntry *tlb_entry, + target_ulong start, + target_ulong length) { uintptr_t addr = tlb_entry->addr_write; - if ((addr & (TLB_INVALID_MASK | TLB_MMIO | - TLB_DISCARD_WRITE | TLB_NOTDIRTY)) == 0) { + if ((addr & (TLB_INVALID_MASK | TLB_MMIO | TLB_DISCARD_WRITE | + TLB_NOTDIRTY)) == 0) { addr &= TARGET_PAGE_MASK; if ((addr - start) < length) { #if TCG_OVERSIZED_GUEST @@ -680,7 +734,6 @@ static void tlb_reset_dirty_range_by_vaddr_locked(struct uc_struct *uc, CPUTLBEn } } - /* * Called with tlb_c.lock held. * Called only from the vCPU context, i.e. the TLB's owner thread. @@ -713,13 +766,15 @@ void tlb_reset_dirty(CPUState *cpu, ram_addr_t start1, ram_addr_t length) } for (i = 0; i < CPU_VTLB_SIZE; i++) { - tlb_reset_dirty_range_locked(uc, &env_tlb(env)->d[mmu_idx].vtable[i], - start1, length); + tlb_reset_dirty_range_locked( + uc, &env_tlb(env)->d[mmu_idx].vtable[i], start1, length); } } + tlb_mutated(env); } -void tlb_reset_dirty_by_vaddr(CPUState *cpu, target_ulong start1, target_ulong length) +void tlb_reset_dirty_by_vaddr(CPUState *cpu, target_ulong start1, + target_ulong length) { struct uc_struct *uc = cpu->uc; CPUArchState *env; @@ -732,23 +787,24 @@ void tlb_reset_dirty_by_vaddr(CPUState *cpu, target_ulong start1, target_ulong l unsigned int n = tlb_n_entries(&env_tlb(env)->f[mmu_idx]); for (i = 0; i < n; i++) { - tlb_reset_dirty_range_by_vaddr_locked(uc, &env_tlb(env)->f[mmu_idx].table[i], - start1, length); + tlb_reset_dirty_range_by_vaddr_locked( + uc, &env_tlb(env)->f[mmu_idx].table[i], start1, length); } for (i = 0; i < CPU_VTLB_SIZE; i++) { - tlb_reset_dirty_range_by_vaddr_locked(uc, &env_tlb(env)->d[mmu_idx].vtable[i], - start1, length); + tlb_reset_dirty_range_by_vaddr_locked( + uc, &env_tlb(env)->d[mmu_idx].vtable[i], start1, length); } } + tlb_mutated(env); } /* Called with tlb_c.lock held */ static inline void tlb_set_dirty1_locked(CPUTLBEntry *tlb_entry, target_ulong vaddr) { - if (tlb_entry->addr_write == (vaddr | TLB_NOTDIRTY)) { - tlb_entry->addr_write = vaddr; + if ((tlb_entry->addr_write & ~TLB_FORCE_SLOW) == (vaddr | TLB_NOTDIRTY)) { + tlb_entry->addr_write &= ~TLB_NOTDIRTY; } } @@ -775,6 +831,7 @@ void tlb_set_dirty(CPUState *cpu, target_ulong vaddr) tlb_set_dirty1_locked(&env_tlb(env)->d[mmu_idx].vtable[k], vaddr); } } + tlb_mutated(env); } /* Our TLB does not support large pages, so remember the area covered by @@ -801,6 +858,36 @@ static void tlb_add_large_page(CPUArchState *env, int mmu_idx, env_tlb(env)->d[mmu_idx].large_page_mask = lp_mask; } +static inline void tlb_set_compare(CPUTLBEntryFull *full, CPUTLBEntry *entry, + target_ulong address, unsigned slow_flags, + MMUAccessType access_type, bool enabled) +{ + if (enabled) { + slow_flags &= TLB_SLOW_FLAGS_MASK; + if (slow_flags != 0) { + address |= TLB_FORCE_SLOW; + } + } else { + address = -1; + slow_flags = 0; + } + + switch (access_type) { + case MMU_DATA_LOAD: + entry->addr_read = address; + break; + case MMU_DATA_STORE: + entry->addr_write = address; + break; + case MMU_INST_FETCH: + entry->addr_code = address; + break; + default: + g_assert_not_reached(); + } + full->slow_flags[access_type] = slow_flags; +} + /* Add a new TLB entry. At most one entry for a given virtual address * is permitted. Only a single TARGET_PAGE_SIZE region is mapped, the * supplied page size is only used by tlb_flush_page. @@ -811,9 +898,7 @@ static void tlb_add_large_page(CPUArchState *env, int mmu_idx, void tlb_set_page_full(CPUState *cpu, int mmu_idx, target_ulong vaddr, CPUTLBEntryFull *full) { -#ifdef TARGET_ARM struct uc_struct *uc = cpu->uc; -#endif CPUArchState *env = cpu->env_ptr; CPUTLB *tlb = env_tlb(env); CPUTLBDesc *desc = &tlb->d[mmu_idx]; @@ -821,10 +906,13 @@ void tlb_set_page_full(CPUState *cpu, int mmu_idx, target_ulong vaddr, unsigned int index; target_ulong address; target_ulong write_address; + unsigned read_slow_flags; + unsigned write_slow_flags; uintptr_t addend; CPUTLBEntry *te, tn; hwaddr iotlb, xlat, sz, paddr_page; target_ulong vaddr_page; + hwaddr paddr_last; int asidx, prot, wp_flags; bool is_ram; @@ -837,22 +925,33 @@ void tlb_set_page_full(CPUState *cpu, int mmu_idx, target_ulong vaddr, tlb_add_large_page(env, mmu_idx, vaddr, sz); } vaddr_page = vaddr & TARGET_PAGE_MASK; - paddr_page = full->phys_addr & TARGET_PAGE_MASK; + paddr_page = full->phys_addr & ~((hwaddr)TARGET_PAGE_SIZE - 1); prot = full->prot; asidx = cpu_asidx_from_attrs(cpu, full->attrs); - section = address_space_translate_for_iotlb(cpu, asidx, paddr_page, - &xlat, &sz, full->attrs, - &prot); + section = address_space_translate_for_iotlb(cpu, asidx, paddr_page, &xlat, + &sz, full->attrs, &prot); assert(sz >= TARGET_PAGE_SIZE); address = vaddr_page; + read_slow_flags = 0; if (full->lg_page_size < TARGET_PAGE_BITS) { /* Repeat the MMU check and TLB fill on every access. */ address |= TLB_INVALID_MASK; } if (full->attrs.byte_swap) { - address |= TLB_BSWAP; + read_slow_flags |= TLB_BSWAP; + } + + paddr_last = paddr_page + TARGET_PAGE_SIZE - 1; + if (paddr_last < paddr_page) { + paddr_last = (hwaddr)-1; + } + if (HOOK_EXISTS_BOUNDED_RANGE(uc, UC_HOOK_MEM_READ, paddr_page, + paddr_last) || + HOOK_EXISTS_BOUNDED_RANGE(uc, UC_HOOK_MEM_READ_AFTER, paddr_page, + paddr_last)) { + read_slow_flags |= TLB_UC_HOOK; } is_ram = memory_region_is_ram(section->mr); @@ -867,6 +966,13 @@ void tlb_set_page_full(CPUState *cpu, int mmu_idx, target_ulong vaddr, } write_address = address; + write_slow_flags = read_slow_flags; + if (HOOK_EXISTS_BOUNDED_RANGE(uc, UC_HOOK_MEM_WRITE, paddr_page, + paddr_last)) { + write_slow_flags |= TLB_UC_HOOK; + } else { + write_slow_flags &= ~TLB_UC_HOOK; + } if (is_ram) { iotlb = memory_region_get_ram_addr(section->mr) + xlat; /* @@ -894,17 +1000,63 @@ void tlb_set_page_full(CPUState *cpu, int mmu_idx, target_ulong vaddr, * but of course reads to I/O must go through MMIO. */ write_address |= TLB_MMIO; - //if (!is_romd) { - address = write_address; + // if (!is_romd) { + address = write_address; //} } - wp_flags = cpu_watchpoint_address_matches(cpu, vaddr_page, - TARGET_PAGE_SIZE); + wp_flags = + cpu_watchpoint_address_matches(cpu, vaddr_page, TARGET_PAGE_SIZE); index = tlb_index(env, mmu_idx, vaddr_page); te = tlb_entry(env, mmu_idx, vaddr_page); + if (uc->tlb_mode == UC_TLB_VIRTUAL && + !tlb_hit_page_anyprot(uc, te, vaddr_page) && !tlb_entry_is_empty(te)) { + CPUTLBDescFast *fast = &tlb->f[mmu_idx]; + size_t old_size = tlb_n_entries(fast); + size_t max_bits = MIN(16, CPU_TLB_DYN_MAX_BITS); + target_ulong resident_page; + + if (tlb_entry_page(uc, te, &resident_page)) { + uint64_t page_xor = + (uint64_t)((resident_page ^ vaddr_page) >> TARGET_PAGE_BITS); + + if (page_xor != 0) { + unsigned needed_bits = ctz64(page_xor) + 1; + + if (needed_bits <= max_bits) { + size_t needed_size = 1ULL << needed_bits; + + desc->collision_min_size = + MAX(desc->collision_min_size, needed_size); + desc->collision_misses++; + } + } + } + + if (desc->collision_misses >= CPU_VTLB_SIZE) { + size_t new_size = desc->collision_min_size; + + if (new_size > old_size) { + int64_t now = get_clock_realtime(); + + tlb_mmu_resize_locked(uc, desc, fast, now, new_size); + tlb_mmu_flush_locked(desc, fast); + desc->collision_floor = tlb_n_entries(fast); + desc->collision_floor_expires_ns = + now + TLB_VIRTUAL_COLLISION_HOLD_NS; + tlb->c.table_epoch++; + tlb_mutated(env); + index = tlb_index(env, mmu_idx, vaddr_page); + te = tlb_entry(env, mmu_idx, vaddr_page); + } else { + desc->collision_misses = 0; + desc->collision_min_size = 0; + } + } + } + /* Note that the tlb is no longer clean. */ tlb->c.dirty |= 1 << mmu_idx; @@ -915,7 +1067,8 @@ void tlb_set_page_full(CPUState *cpu, int mmu_idx, target_ulong vaddr, * Only evict the old entry to the victim tlb if it's for a * different page; otherwise just overwrite the stale data. */ - if (!tlb_hit_page_anyprot(env->uc, te, vaddr_page) && !tlb_entry_is_empty(te)) { + if (!tlb_hit_page_anyprot(env->uc, te, vaddr_page) && + !tlb_entry_is_empty(te)) { unsigned vidx = desc->vindex++ % CPU_VTLB_SIZE; CPUTLBEntry *tv = &desc->vtable[vidx]; @@ -939,51 +1092,47 @@ void tlb_set_page_full(CPUState *cpu, int mmu_idx, target_ulong vaddr, * vaddr we add back in io_readx()/io_writex()/get_page_addr_code(). */ desc->fulltlb[index] = *full; - desc->fulltlb[index].xlat_section = iotlb - vaddr_page; - desc->fulltlb[index].phys_addr = paddr_page; - desc->fulltlb[index].prot = prot; + full = &desc->fulltlb[index]; + full->xlat_section = iotlb - vaddr_page; + full->phys_addr = paddr_page; + full->prot = prot; + full->section_index = memory_region_section_get_iotlb(cpu, section); /* Now calculate the new entry */ tn.addend = addend - vaddr_page; - tn.paddr = paddr_page; - if (prot & PAGE_READ) { - tn.addr_read = address; - if (wp_flags & BP_MEM_READ) { - tn.addr_read |= TLB_WATCHPOINT; - } - } else { - tn.addr_read = -1; - } + tlb_set_compare(full, &tn, address, read_slow_flags & TLB_BSWAP, + MMU_INST_FETCH, prot & PAGE_EXEC); - if (prot & PAGE_EXEC) { - tn.addr_code = address; - } else { - tn.addr_code = -1; + if (wp_flags & BP_MEM_READ) { + address |= TLB_WATCHPOINT; } + tlb_set_compare(full, &tn, address, read_slow_flags, MMU_DATA_LOAD, + prot & PAGE_READ); - tn.addr_write = -1; if (prot & PAGE_WRITE) { - tn.addr_write = write_address; if (prot & PAGE_WRITE_INV) { - tn.addr_write |= TLB_INVALID_MASK; + write_address |= TLB_INVALID_MASK; } if (wp_flags & BP_MEM_WRITE) { - tn.addr_write |= TLB_WATCHPOINT; + write_address |= TLB_WATCHPOINT; } } + tlb_set_compare(full, &tn, write_address, write_slow_flags, MMU_DATA_STORE, + prot & PAGE_WRITE); copy_tlb_helper_locked(te, &tn); tlb_n_used_entries_inc(env, mmu_idx); + tlb_mutated(env); } -void tlb_set_page_with_attrs(CPUState *cpu, target_ulong vaddr, - hwaddr paddr, MemTxAttrs attrs, int prot, - int mmu_idx, target_ulong size) +void tlb_set_page_with_attrs(CPUState *cpu, target_ulong vaddr, hwaddr paddr, + MemTxAttrs attrs, int prot, int mmu_idx, + target_ulong size) { CPUTLBEntryFull full; assert(size != 0 && is_power_of_2(size)); - full = (CPUTLBEntryFull) { + full = (CPUTLBEntryFull){ .phys_addr = paddr, .attrs = attrs, .prot = prot, @@ -996,15 +1145,15 @@ void tlb_set_page_with_attrs(CPUState *cpu, target_ulong vaddr, /* Add a new TLB entry, but without specifying the memory * transaction attributes to be used. */ -void tlb_set_page(CPUState *cpu, target_ulong vaddr, - hwaddr paddr, int prot, +void tlb_set_page(CPUState *cpu, target_ulong vaddr, hwaddr paddr, int prot, int mmu_idx, target_ulong size) { - tlb_set_page_with_attrs(cpu, vaddr, paddr, MEMTXATTRS_UNSPECIFIED, - prot, mmu_idx, size); + tlb_set_page_with_attrs(cpu, vaddr, paddr, MEMTXATTRS_UNSPECIFIED, prot, + mmu_idx, size); } -static inline ram_addr_t qemu_ram_addr_from_host_nofail(struct uc_struct *uc, void *ptr) +static inline ram_addr_t qemu_ram_addr_from_host_nofail(struct uc_struct *uc, + void *ptr) { ram_addr_t ram_addr; @@ -1031,8 +1180,8 @@ static void tlb_fill(CPUState *cpu, target_ulong addr, int size, * This is not a probe, so only valid return is success; failure * should result in exception + longjmp to the cpu loop. */ - ok = cpu_tcg_tlb_fill(cpu, addr, size, access_type, mmu_idx, - false, retaddr); + ok = + cpu_tcg_tlb_fill(cpu, addr, size, access_type, mmu_idx, false, retaddr); assert(ok); #else cpu_tcg_tlb_fill(cpu, addr, size, access_type, mmu_idx, false, retaddr); @@ -1070,8 +1219,8 @@ static uint64_t io_readx(CPUArchState *env, CPUTLBEntryFull *iotlbentry, uint64_t val; MemTxResult r; - section = iotlb_to_section(cpu, iotlbentry->xlat_section, - iotlbentry->attrs); + section = + iotlb_to_section(cpu, iotlbentry->xlat_section, iotlbentry->attrs); mr = section->mr; mr_offset = (iotlbentry->xlat_section & TARGET_PAGE_MASK) + addr; cpu->mem_io_pc = retaddr; @@ -1079,11 +1228,12 @@ static uint64_t io_readx(CPUArchState *env, CPUTLBEntryFull *iotlbentry, cpu_io_recompile(cpu, retaddr); } - r = memory_region_dispatch_read(uc, mr, mr_offset, &val, op, iotlbentry->attrs); + uc->tb_exec_frame_publish(uc, retaddr); + r = memory_region_dispatch_read(uc, mr, mr_offset, &val, op, + iotlbentry->attrs); if (r != MEMTX_OK) { - hwaddr physaddr = mr_offset + - section->offset_within_address_space - - section->offset_within_region; + hwaddr physaddr = mr_offset + section->offset_within_address_space - + section->offset_within_region; cpu_transaction_failed(cpu, physaddr, addr, memop_size(op), access_type, mmu_idx, iotlbentry->attrs, r, retaddr); @@ -1103,8 +1253,8 @@ static void io_writex(CPUArchState *env, CPUTLBEntryFull *iotlbentry, MemoryRegion *mr; MemTxResult r; - section = iotlb_to_section(cpu, iotlbentry->xlat_section, - iotlbentry->attrs); + section = + iotlb_to_section(cpu, iotlbentry->xlat_section, iotlbentry->attrs); mr = section->mr; mr_offset = (iotlbentry->xlat_section & TARGET_PAGE_MASK) + addr; if (!cpu->can_do_io) { @@ -1112,11 +1262,12 @@ static void io_writex(CPUArchState *env, CPUTLBEntryFull *iotlbentry, } cpu->mem_io_pc = retaddr; - r = memory_region_dispatch_write(uc, mr, mr_offset, val, op, iotlbentry->attrs); + uc->tb_exec_frame_publish(uc, retaddr); + r = memory_region_dispatch_write(uc, mr, mr_offset, val, op, + iotlbentry->attrs); if (r != MEMTX_OK) { - hwaddr physaddr = mr_offset + - section->offset_within_address_space - - section->offset_within_region; + hwaddr physaddr = mr_offset + section->offset_within_address_space - + section->offset_within_region; cpu_transaction_failed(cpu, physaddr, addr, memop_size(op), MMU_DATA_STORE, mmu_idx, iotlbentry->attrs, r, @@ -1151,7 +1302,7 @@ static bool victim_tlb_hit(CPUArchState *env, size_t mmu_idx, size_t index, cmp = *(target_ulong *)((uintptr_t)vtlb + elt_ofs); #endif - if (cmp == page) { + if (tlb_hit_page(env->uc, cmp, page)) { /* Found entry in victim tlb, swap tlb and iotlb. */ CPUTLBEntry tmptlb, *tlb = &env_tlb(env)->f[mmu_idx].table[index]; @@ -1168,6 +1319,7 @@ static bool victim_tlb_hit(CPUArchState *env, size_t mmu_idx, size_t index, tmpf = *f1; *f1 = *f2; *f2 = tmpf; + tlb_mutated(env); return true; } } @@ -1175,9 +1327,9 @@ static bool victim_tlb_hit(CPUArchState *env, size_t mmu_idx, size_t index, } /* Macro to call the above, with local variables from the use context. */ -#define VICTIM_TLB_HIT(TY, ADDR) \ - victim_tlb_hit(env, mmu_idx, index, offsetof(CPUTLBEntry, TY), \ - (ADDR) & TARGET_PAGE_MASK) +#define VICTIM_TLB_HIT(TY, ADDR) \ + victim_tlb_hit(env, mmu_idx, index, offsetof(CPUTLBEntry, TY), \ + (ADDR) & TARGET_PAGE_MASK) /* * Return a ram_addr_t for the virtual address for execution. @@ -1235,6 +1387,24 @@ tb_page_addr_t get_page_addr_code(CPUArchState *env, target_ulong addr) return get_page_addr_code_hostp(env, addr, NULL); } +static inline CPUTLBEntryFull * +tlb_full_entry(CPUArchState *env, uintptr_t mmu_idx, uintptr_t index) +{ + return &env_tlb(env)->d[mmu_idx].fulltlb[index]; +} + +static inline MemoryRegion *tlb_full_memory_region(CPUState *cpu, + CPUTLBEntryFull *full) +{ + MemoryRegionSection *section; + + section = iotlb_to_section(cpu, full->section_index, full->attrs); + if (section->mr == &cpu->uc->io_mem_unassigned) { + return NULL; + } + return section->mr; +} + static void notdirty_write(CPUState *cpu, vaddr mem_vaddr, unsigned size, CPUTLBEntryFull *iotlbentry, uintptr_t retaddr, CPUTLBEntry *tlbe) @@ -1243,11 +1413,12 @@ static void notdirty_write(CPUState *cpu, vaddr mem_vaddr, unsigned size, struct uc_struct *uc = cpu->uc; #endif ram_addr_t ram_addr = mem_vaddr + iotlbentry->xlat_section; - MemoryRegion *mr = cpu->uc->memory_mapping(cpu->uc, tlbe->paddr | (mem_vaddr & ~TARGET_PAGE_MASK)); + hwaddr paddr = iotlbentry->phys_addr | (mem_vaddr & ~TARGET_PAGE_MASK); + MemoryRegion *mr = tlb_full_memory_region(cpu, iotlbentry); if (mr && (mr->perms & UC_PROT_EXEC) != 0) { - struct page_collection *pages - = page_collection_lock(cpu->uc, ram_addr, ram_addr + size); + struct page_collection *pages = + page_collection_lock(cpu->uc, ram_addr, ram_addr + size); tb_invalidate_phys_page_fast(cpu->uc, pages, ram_addr, size, retaddr); page_collection_unlock(pages); } @@ -1257,19 +1428,18 @@ static void notdirty_write(CPUState *cpu, vaddr mem_vaddr, unsigned size, // - have memory hooks installed // - or doing snapshot // , then never clean the tlb - if (!(!mr || (tlbe->addr_write != -1 && mr->priority < cpu->uc->snapshot_level)) && - !(tlbe->addr_code != -1) && - !uc_mem_hook_installed(cpu->uc, tlbe->paddr | (mem_vaddr & ~TARGET_PAGE_MASK))) { + if (!(!mr || (tlbe->addr_write != -1 && + cpu->uc->memory_context_count != 0 && + mr->priority < cpu->uc->snapshot_level)) && + !(tlbe->addr_code != -1) && !uc_mem_hook_installed(cpu->uc, paddr)) { tlb_set_dirty(cpu, mem_vaddr); } } static int probe_access_internal(CPUArchState *env, target_ulong addr, - int fault_size, - MMUAccessType access_type, int mmu_idx, - bool nonfault, void **phost, - CPUTLBEntryFull **pfull, - uintptr_t retaddr) + int fault_size, MMUAccessType access_type, + int mmu_idx, bool nonfault, void **phost, + CPUTLBEntryFull **pfull, uintptr_t retaddr) { #ifdef TARGET_ARM struct uc_struct *uc = env->uc; @@ -1278,7 +1448,7 @@ static int probe_access_internal(CPUArchState *env, target_ulong addr, CPUTLBEntry *entry = tlb_entry(env, mmu_idx, addr); target_ulong tlb_addr; size_t elt_ofs; - int flags = TLB_FLAGS_MASK; + int flags = TLB_FLAGS_MASK & ~TLB_FORCE_SLOW; switch (access_type) { case MMU_DATA_LOAD: @@ -1300,8 +1470,8 @@ static int probe_access_internal(CPUArchState *env, target_ulong addr, addr & TARGET_PAGE_MASK)) { CPUState *cpu = env_cpu(env); - if (!cpu_tcg_tlb_fill(cpu, addr, fault_size, access_type, - mmu_idx, nonfault, retaddr)) { + if (!cpu_tcg_tlb_fill(cpu, addr, fault_size, access_type, mmu_idx, + nonfault, retaddr)) { *phost = NULL; *pfull = NULL; return TLB_INVALID_MASK; @@ -1319,6 +1489,7 @@ static int probe_access_internal(CPUArchState *env, target_ulong addr, flags &= tlb_addr; *pfull = &env_tlb(env)->d[mmu_idx].fulltlb[index]; + flags |= (*pfull)->slow_flags[access_type] & ~TLB_UC_HOOK; /* Fold all MMIO-like slow paths into TLB_MMIO. */ if (unlikely(flags & ~(TLB_WATCHPOINT | TLB_NOTDIRTY))) { @@ -1331,14 +1502,13 @@ static int probe_access_internal(CPUArchState *env, target_ulong addr, } int probe_access_full(CPUArchState *env, target_ulong addr, - MMUAccessType access_type, int mmu_idx, - bool nonfault, void **phost, - CPUTLBEntryFull **pfull, uintptr_t retaddr) + MMUAccessType access_type, int mmu_idx, bool nonfault, + void **phost, CPUTLBEntryFull **pfull, uintptr_t retaddr) { int flags; - flags = probe_access_internal(env, addr, 0, access_type, mmu_idx, - nonfault, phost, pfull, retaddr); + flags = probe_access_internal(env, addr, 0, access_type, mmu_idx, nonfault, + phost, pfull, retaddr); if (unlikely(flags & TLB_NOTDIRTY)) { CPUTLBEntry *entry = tlb_entry(env, mmu_idx, addr); @@ -1349,13 +1519,13 @@ int probe_access_full(CPUArchState *env, target_ulong addr, } int probe_access_flags(CPUArchState *env, target_ulong addr, - MMUAccessType access_type, int mmu_idx, - bool nonfault, void **phost, uintptr_t retaddr) + MMUAccessType access_type, int mmu_idx, bool nonfault, + void **phost, uintptr_t retaddr) { CPUTLBEntryFull *full; - return probe_access_full(env, addr, access_type, mmu_idx, nonfault, - phost, &full, retaddr); + return probe_access_full(env, addr, access_type, mmu_idx, nonfault, phost, + &full, retaddr); } /* @@ -1379,19 +1549,19 @@ void *probe_access(CPUArchState *env, target_ulong addr, int size, g_assert(-(addr | TARGET_PAGE_MASK) >= size); #endif - flags = probe_access_internal(env, addr, size, access_type, mmu_idx, - false, &host, &full, retaddr); + flags = probe_access_internal(env, addr, size, access_type, mmu_idx, false, + &host, &full, retaddr); if (size == 0) { return NULL; } entry = tlb_entry(env, mmu_idx, addr); if (unlikely(flags & TLB_WATCHPOINT)) { - int wp_access = access_type == MMU_DATA_STORE ? - BP_MEM_WRITE : BP_MEM_READ; + int wp_access = + access_type == MMU_DATA_STORE ? BP_MEM_WRITE : BP_MEM_READ; - cpu_check_watchpoint(env_cpu(env), addr, size, full->attrs, - wp_access, retaddr); + cpu_check_watchpoint(env_cpu(env), addr, size, full->attrs, wp_access, + retaddr); } if (unlikely(flags & TLB_NOTDIRTY)) { notdirty_write(env_cpu(env), addr, size, full, retaddr, entry); @@ -1401,10 +1571,12 @@ void *probe_access(CPUArchState *env, target_ulong addr, int size, } bool tlb_vaddr_to_paddr(CPUArchState *env, abi_ptr addr, - MMUAccessType access_type, int mmu_idx, target_ulong *paddr) + MMUAccessType access_type, int mmu_idx, hwaddr *paddr) { struct uc_struct *uc = env->uc; + uintptr_t index = tlb_index(env, mmu_idx, addr); CPUTLBEntry *entry = tlb_entry(env, mmu_idx, addr); + CPUTLBEntryFull *full; target_ulong tlb_addr, page; size_t elt_ofs = 0; @@ -1426,23 +1598,22 @@ bool tlb_vaddr_to_paddr(CPUArchState *env, abi_ptr addr, tlb_addr = tlb_read_ofs(entry, elt_ofs); if (!tlb_hit_page(uc, tlb_addr, page)) { - uintptr_t index = tlb_index(env, mmu_idx, addr); - if (!victim_tlb_hit(env, mmu_idx, index, elt_ofs, page)) { CPUState *cs = env_cpu(env); - if (!cpu_tcg_tlb_fill(cs, addr, 0, access_type, mmu_idx, true, - 0)) { + if (!cpu_tcg_tlb_fill(cs, addr, 0, access_type, mmu_idx, true, 0)) { /* Non-faulting page table read failed. */ return false; } /* TLB resize via tlb_fill may have moved the entry. */ + index = tlb_index(env, mmu_idx, addr); entry = tlb_entry(env, mmu_idx, addr); } tlb_addr = tlb_read_ofs(entry, elt_ofs); } - *paddr = entry->paddr | (addr & ~TARGET_PAGE_MASK); + full = &env_tlb(env)->d[mmu_idx].fulltlb[index]; + *paddr = full->phys_addr | (addr & ~TARGET_PAGE_MASK); return true; } @@ -1450,7 +1621,9 @@ void *tlb_vaddr_to_host(CPUArchState *env, abi_ptr addr, MMUAccessType access_type, int mmu_idx) { struct uc_struct *uc = env->uc; + uintptr_t index = tlb_index(env, mmu_idx, addr); CPUTLBEntry *entry = tlb_entry(env, mmu_idx, addr); + CPUTLBEntryFull *full; target_ulong tlb_addr, page; size_t elt_ofs = 0; @@ -1472,26 +1645,28 @@ void *tlb_vaddr_to_host(CPUArchState *env, abi_ptr addr, tlb_addr = tlb_read_ofs(entry, elt_ofs); if (!tlb_hit_page(uc, tlb_addr, page)) { - uintptr_t index = tlb_index(env, mmu_idx, addr); - if (!victim_tlb_hit(env, mmu_idx, index, elt_ofs, page)) { CPUState *cs = env_cpu(env); - if (!cpu_tcg_tlb_fill(cs, addr, 0, access_type, mmu_idx, true, - 0)) { + if (!cpu_tcg_tlb_fill(cs, addr, 0, access_type, mmu_idx, true, 0)) { /* Non-faulting page table read failed. */ return NULL; } /* TLB resize via tlb_fill may have moved the entry. */ + index = tlb_index(env, mmu_idx, addr); entry = tlb_entry(env, mmu_idx, addr); } tlb_addr = tlb_read_ofs(entry, elt_ofs); } - if (tlb_addr & ~TARGET_PAGE_MASK) { + if (tlb_addr & ~(TARGET_PAGE_MASK | TLB_FORCE_SLOW)) { /* IO access */ return NULL; } + full = tlb_full_entry(env, mmu_idx, index); + if (full->slow_flags[access_type] & ~TLB_UC_HOOK) { + return NULL; + } return (void *)((uintptr_t)addr + entry->addend); } @@ -1511,6 +1686,7 @@ static void *atomic_mmu_lookup(CPUArchState *env, target_ulong addr, MemOp mop = get_memop(oi); int a_bits = get_alignment_bits(mop); int s_bits = mop & MO_SIZE; + CPUTLBEntryFull *full; void *hostaddr; /* Adjust the given return address. */ @@ -1519,8 +1695,8 @@ static void *atomic_mmu_lookup(CPUArchState *env, target_ulong addr, /* Enforce guest required alignment. */ if (unlikely(a_bits > 0 && (addr & ((1 << a_bits) - 1)))) { /* ??? Maybe indicate atomic op to cpu_unaligned_access */ - cpu_tcg_unaligned_access(env_cpu(env), addr, MMU_DATA_STORE, - mmu_idx, retaddr); + cpu_tcg_unaligned_access(env_cpu(env), addr, MMU_DATA_STORE, mmu_idx, + retaddr); } /* Enforce qemu required alignment. */ @@ -1535,8 +1711,8 @@ static void *atomic_mmu_lookup(CPUArchState *env, target_ulong addr, /* Check TLB entry and enforce page permissions. */ if (!tlb_hit(env->uc, tlb_addr, addr)) { if (!VICTIM_TLB_HIT(addr_write, addr)) { - tlb_fill(env_cpu(env), addr, 1 << s_bits, MMU_DATA_STORE, - mmu_idx, retaddr); + tlb_fill(env_cpu(env), addr, 1 << s_bits, MMU_DATA_STORE, mmu_idx, + retaddr); index = tlb_index(env, mmu_idx, addr); tlbe = tlb_entry(env, mmu_idx, addr); } @@ -1550,10 +1726,16 @@ static void *atomic_mmu_lookup(CPUArchState *env, target_ulong addr, goto stop_the_world; } + full = tlb_full_entry(env, mmu_idx, index); + if (unlikely(full->slow_flags[MMU_DATA_STORE] & TLB_BSWAP)) { + goto stop_the_world; + } + /* Let the guest notice RMW on a write-only page. */ - if (unlikely(tlbe->addr_read != (tlb_addr & ~TLB_NOTDIRTY))) { - tlb_fill(env_cpu(env), addr, 1 << s_bits, MMU_DATA_LOAD, - mmu_idx, retaddr); + if (unlikely((tlbe->addr_read & ~TLB_FORCE_SLOW) != + (tlb_addr & ~(TLB_NOTDIRTY | TLB_FORCE_SLOW)))) { + tlb_fill(env_cpu(env), addr, 1 << s_bits, MMU_DATA_LOAD, mmu_idx, + retaddr); /* Since we don't support reads and writes to different addresses, and we do have the proper page loaded for write, this shouldn't ever return. But just in case, handle via stop-the-world. */ @@ -1563,14 +1745,12 @@ static void *atomic_mmu_lookup(CPUArchState *env, target_ulong addr, hostaddr = (void *)((uintptr_t)addr + tlbe->addend); if (unlikely(tlb_addr & TLB_NOTDIRTY)) { - notdirty_write(env_cpu(env), addr, 1 << s_bits, - &env_tlb(env)->d[mmu_idx].fulltlb[index], retaddr, - tlbe); + notdirty_write(env_cpu(env), addr, 1 << s_bits, full, retaddr, tlbe); } return hostaddr; - stop_the_world: +stop_the_world: cpu_loop_exit_atomic(env_cpu(env), retaddr); } @@ -1588,6 +1768,8 @@ typedef uint64_t FullLoadHelper(CPUArchState *env, target_ulong addr, typedef struct CPUTLBHookState { bool synced; + bool epoch_valid; + uint64_t mutation_epoch; #ifdef TARGET_ARM uint32_t condexec_bits; uint32_t restored_condexec_bits; @@ -1598,6 +1780,8 @@ static inline void tlb_hook_state_init(CPUArchState *env, CPUTLBHookState *state) { state->synced = false; + state->epoch_valid = false; + state->mutation_epoch = 0; #ifdef TARGET_ARM state->condexec_bits = env->condexec_bits; state->restored_condexec_bits = env->condexec_bits; @@ -1609,6 +1793,7 @@ static inline void tlb_hook_state_sync(CPUArchState *env, uintptr_t retaddr, { struct uc_struct *uc = env->uc; + uc->tb_exec_frame_publish(uc, retaddr); if (!state->synced && !uc->skip_sync_pc_on_exit && retaddr) { cpu_restore_state(uc->cpu, retaddr, false); state->synced = true; @@ -1621,18 +1806,62 @@ static inline void tlb_hook_state_sync(CPUArchState *env, uintptr_t retaddr, env->condexec_bits = state->restored_condexec_bits; } #endif + if (!state->epoch_valid) { + state->mutation_epoch = env_tlb(env)->c.mutation_epoch; + state->epoch_valid = true; + } +} + +static inline bool tlb_hook_state_changed(CPUArchState *env, + CPUTLBHookState *state) +{ + bool changed; + + if (!state->epoch_valid) { + return false; + } + changed = state->mutation_epoch != env_tlb(env)->c.mutation_epoch; + state->epoch_valid = false; + return changed; } static inline void tlb_hook_state_restore(CPUArchState *env, CPUTLBHookState *state) { #ifdef TARGET_ARM - if (state->synced && !env->uc->stop_request) { + if (state->synced && !uc_stop_requested(env->uc)) { env->condexec_bits = state->condexec_bits; } #endif } +static inline bool tlb_exit_after_stopped_hook(CPUArchState *env, + uintptr_t retaddr) +{ + struct uc_struct *uc = env->uc; + + if (!uc_stop_requested(uc)) { + return false; + } + + uc->invalid_error = UC_ERR_OK; + if (!uc->size_recur_mem && uc->nested_level > 0 && !uc->cpu->stopped) { + cpu_loop_exit_restore(uc->cpu, retaddr); + } + return true; +} + +static inline void cputlb_exit_request(CPUArchState *env, TCGMemOpIdx oi, + uintptr_t retaddr) +{ + struct uc_struct *uc = env->uc; + + if (memop_idx_has_exit_request(oi) && !uc->size_recur_mem && + qatomic_read(&uc->cpu->exit_request_pending)) { + cpu_tcg_exit_request(uc, retaddr); + } +} + static inline target_ulong tlb_addr_for_access(const CPUTLBEntry *entry, MMUAccessType access_type) { @@ -1647,12 +1876,22 @@ static inline target_ulong tlb_addr_for_access(const CPUTLBEntry *entry, } } +static inline hwaddr tlb_phys_addr(CPUArchState *env, uintptr_t mmu_idx, + uintptr_t index, target_ulong addr) +{ +#ifdef TARGET_PAGE_BITS_VARY + struct uc_struct *uc = env->uc; +#endif + CPUTLBEntryFull *full = tlb_full_entry(env, mmu_idx, index); + + return full->phys_addr | (addr & ~TARGET_PAGE_MASK); +} + static inline void tlb_revalidate_entry(CPUArchState *env, uintptr_t mmu_idx, target_ulong addr, size_t size, MMUAccessType access_type, size_t tlb_off, uintptr_t retaddr, - uintptr_t *index, - CPUTLBEntry **entry, + uintptr_t *index, CPUTLBEntry **entry, target_ulong *tlb_addr) { struct uc_struct *uc = env->uc; @@ -1676,8 +1915,38 @@ static inline void tlb_revalidate_entry(CPUArchState *env, uintptr_t mmu_idx, *tlb_addr &= ~TLB_INVALID_MASK; } -static inline uint64_t -load_memop(const void *haddr, MemOp op) +static inline void tlb_revalidate_after_hook( + CPUArchState *env, CPUTLBHookState *state, uintptr_t mmu_idx, + target_ulong addr, size_t size, MMUAccessType access_type, size_t tlb_off, + uintptr_t retaddr, uintptr_t *index, CPUTLBEntry **entry, + target_ulong *tlb_addr, hwaddr *paddr, MemoryRegion **mr) +{ + if (!tlb_hook_state_changed(env, state)) { + return; + } + + tlb_revalidate_entry(env, mmu_idx, addr, size, access_type, tlb_off, + retaddr, index, entry, tlb_addr); + *paddr = tlb_phys_addr(env, mmu_idx, *index, addr); + *mr = tlb_full_memory_region(env_cpu(env), + tlb_full_entry(env, mmu_idx, *index)); +} + +static inline bool tlb_hook_only_ram_access(struct uc_struct *uc, + MemoryRegion *mr, target_ulong addr, + size_t size, target_ulong tlb_addr, + unsigned slow_flags) +{ + target_ulong page_offset = addr & ~TARGET_PAGE_MASK; + + return !uc->size_recur_mem && uc->memory_context_count == 0 && mr != NULL && + memory_region_is_ram(mr) && (addr & (size - 1)) == 0 && + page_offset + size <= TARGET_PAGE_SIZE && + (tlb_addr & ~TARGET_PAGE_MASK) == TLB_FORCE_SLOW && + slow_flags == TLB_UC_HOOK; +} + +static inline uint64_t load_memop(const void *haddr, MemOp op) { switch (op) { case MO_UB: @@ -1700,23 +1969,22 @@ load_memop(const void *haddr, MemOp op) } } -static inline uint64_t -load_helper(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi, - uintptr_t retaddr, MemOp op, bool code_read, - FullLoadHelper *full_load) +static inline uint64_t load_helper(CPUArchState *env, target_ulong addr, + TCGMemOpIdx oi, uintptr_t retaddr, MemOp op, + bool code_read, FullLoadHelper *full_load) { uintptr_t mmu_idx = get_mmuidx(oi); uintptr_t index = tlb_index(env, mmu_idx, addr); CPUTLBEntry *entry = tlb_entry(env, mmu_idx, addr); target_ulong tlb_addr = code_read ? entry->addr_code : entry->addr_read; hwaddr paddr; - const size_t tlb_off = code_read ? - offsetof(CPUTLBEntry, addr_code) : offsetof(CPUTLBEntry, addr_read); + const size_t tlb_off = code_read ? offsetof(CPUTLBEntry, addr_code) + : offsetof(CPUTLBEntry, addr_read); const MMUAccessType access_type = code_read ? MMU_INST_FETCH : MMU_DATA_LOAD; unsigned a_bits = get_alignment_bits(get_memop(oi)); void *haddr; - uint64_t res; + uint64_t res = 0; size_t size = memop_size(op); int error_code; struct hook *hook; @@ -1724,22 +1992,22 @@ load_helper(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi, HOOK_FOREACH_VAR_DECLARE; struct uc_struct *uc = env->uc; MemoryRegion *mr; + unsigned slow_flags; CPUTLBHookState hook_state; tlb_hook_state_init(env, &hook_state); /* Handle CPU specific unaligned behaviour */ if (addr & ((1 << a_bits) - 1)) { - cpu_tcg_unaligned_access(env_cpu(env), addr, access_type, - mmu_idx, retaddr); + cpu_tcg_unaligned_access(env_cpu(env), addr, access_type, mmu_idx, + retaddr); } /* If the TLB entry is for a different page, reload and try again. */ if (!tlb_hit(env->uc, tlb_addr, addr)) { if (!victim_tlb_hit(env, mmu_idx, index, tlb_off, addr & TARGET_PAGE_MASK)) { - tlb_fill(env_cpu(env), addr, size, - access_type, mmu_idx, retaddr); + tlb_fill(env_cpu(env), addr, size, access_type, mmu_idx, retaddr); index = tlb_index(env, mmu_idx, addr); entry = tlb_entry(env, mmu_idx, addr); } @@ -1747,8 +2015,9 @@ load_helper(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi, tlb_addr &= ~TLB_INVALID_MASK; } - paddr = entry->paddr | (addr & ~TARGET_PAGE_MASK); - mr = uc->memory_mapping(uc, paddr); + paddr = tlb_phys_addr(env, mmu_idx, index, addr); + mr = tlb_full_memory_region(env_cpu(env), + tlb_full_entry(env, mmu_idx, index)); // memory might be still unmapped while reading or fetching if (mr == NULL) { @@ -1756,39 +2025,45 @@ load_helper(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi, // if there is already an unhandled eror, skip callbacks. if (uc->invalid_error == UC_ERR_OK) { if (code_read) { - // code fetching + // code fetching error_code = UC_ERR_FETCH_UNMAPPED; - HOOK_FOREACH(uc, hook, UC_HOOK_MEM_FETCH_UNMAPPED) { + HOOK_FOREACH(uc, hook, UC_HOOK_MEM_FETCH_UNMAPPED) + { if (hook->to_delete) continue; if (!HOOK_BOUND_CHECK(hook, paddr)) continue; tlb_hook_state_sync(env, retaddr, &hook_state); JIT_CALLBACK_GUARD_VAR(handled, - ((uc_cb_eventmem_t)hook->callback)(uc, UC_MEM_FETCH_UNMAPPED, paddr, size, 0, hook->user_data)); + ((uc_cb_eventmem_t)hook->callback)( + uc, UC_MEM_FETCH_UNMAPPED, paddr, + size, 0, hook->user_data)); if (handled) break; // the last callback may already asked to stop emulation - if (uc->stop_request) + if (uc_stop_requested(uc)) break; } } else { // data reading error_code = UC_ERR_READ_UNMAPPED; - HOOK_FOREACH(uc, hook, UC_HOOK_MEM_READ_UNMAPPED) { + HOOK_FOREACH(uc, hook, UC_HOOK_MEM_READ_UNMAPPED) + { if (hook->to_delete) continue; if (!HOOK_BOUND_CHECK(hook, paddr)) continue; tlb_hook_state_sync(env, retaddr, &hook_state); - JIT_CALLBACK_GUARD_VAR(handled, - ((uc_cb_eventmem_t)hook->callback)(uc, UC_MEM_READ_UNMAPPED, paddr, size, 0, hook->user_data)); + JIT_CALLBACK_GUARD_VAR(handled, + ((uc_cb_eventmem_t)hook->callback)( + uc, UC_MEM_READ_UNMAPPED, paddr, + size, 0, hook->user_data)); if (handled) break; // the last callback may already asked to stop emulation - if (uc->stop_request) + if (uc_stop_requested(uc)) break; } } @@ -1796,36 +2071,42 @@ load_helper(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi, error_code = uc->invalid_error; } + if (tlb_exit_after_stopped_hook(env, retaddr)) { + goto load_exit; + } + if (handled) { uc->invalid_error = UC_ERR_OK; - tlb_revalidate_entry(env, mmu_idx, addr, size, access_type, - tlb_off, retaddr, &index, &entry, - &tlb_addr); - paddr = entry->paddr | (addr & ~TARGET_PAGE_MASK); - mr = uc->memory_mapping(uc, paddr); + tlb_revalidate_after_hook(env, &hook_state, mmu_idx, addr, size, + access_type, tlb_off, retaddr, &index, + &entry, &tlb_addr, &paddr, &mr); if (mr == NULL) { uc->invalid_error = UC_ERR_MAP; if (uc->nested_level > 0 && !uc->cpu->stopped) { cpu_exit(uc->cpu); - // XXX(@lazymio): We have to exit early so that the target register won't be overwritten + // XXX(@lazymio): We have to exit early so that the target + // register won't be overwritten // because qemu might generate tcg code like: - // qemu_ld_i64 x0,x1,leq,8 sync: 0 dead: 0 1 - // where we don't have a change to recover x0 value + // qemu_ld_i64 x0,x1,leq,8 sync: 0 + // dead: 0 1 + // where we don't have a change to recover x0 + // value cpu_loop_exit_restore(uc->cpu, retaddr); } - return 0; + goto load_exit; } tlb_hook_state_restore(env, &hook_state); } else { uc->invalid_addr = paddr; uc->invalid_error = error_code; - // printf("***** Invalid fetch (unmapped memory) at " TARGET_FMT_lx "\n", addr); + // printf("***** Invalid fetch (unmapped memory) at " TARGET_FMT_lx + // "\n", addr); if (uc->nested_level > 0 && !uc->cpu->stopped) { cpu_exit(uc->cpu); // See comments above cpu_loop_exit_restore(uc->cpu, retaddr); } - return 0; + goto load_exit; } } @@ -1833,113 +2114,138 @@ load_helper(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi, // patch issue #1041 multiple UC_HOOK_MEM callbacks for unaligned access if (!code_read && !uc->size_recur_mem) { // this is date reading - HOOK_FOREACH(uc, hook, UC_HOOK_MEM_READ) { + cur = hook_dispatch_first_match(uc, UC_HOOK_MEM_READ_IDX, paddr); + for (; cur != NULL && (hook = (struct hook *)cur->data); + cur = cur->next) { if (hook->to_delete) continue; if (!HOOK_BOUND_CHECK(hook, paddr)) continue; tlb_hook_state_sync(env, retaddr, &hook_state); - JIT_CALLBACK_GUARD(((uc_cb_hookmem_t)hook->callback)(env->uc, UC_MEM_READ, paddr, size, 0, hook->user_data)); + JIT_CALLBACK_GUARD(((uc_cb_hookmem_t)hook->callback)( + env->uc, UC_MEM_READ, paddr, size, 0, hook->user_data)); // the last callback may already asked to stop emulation - if (uc->stop_request) + if (uc_stop_requested(uc)) break; } + if (tlb_exit_after_stopped_hook(env, retaddr)) { + goto load_exit; + } tlb_hook_state_restore(env, &hook_state); /* Unicorn: Previous callbacks may invalidate TLB, reload everything. This may have impact on performance but generally fine. A better approach is not always invalidating tlb but this - might cause more chaos regarding re-entry (nested uc_emu_start). + might cause more chaos regarding re-entry (nested + uc_emu_start). */ - tlb_revalidate_entry(env, mmu_idx, addr, size, access_type, tlb_off, - retaddr, &index, &entry, &tlb_addr); - paddr = entry->paddr | (addr & ~TARGET_PAGE_MASK); - mr = uc->memory_mapping(uc, paddr); + tlb_revalidate_after_hook(env, &hook_state, mmu_idx, addr, size, + access_type, tlb_off, retaddr, &index, &entry, + &tlb_addr, &paddr, &mr); // callback on non-readable memory - if (mr != NULL && !(mr->perms & UC_PROT_READ)) { //non-readable + if (mr != NULL && !(mr->perms & UC_PROT_READ)) { // non-readable handled = false; - HOOK_FOREACH(uc, hook, UC_HOOK_MEM_READ_PROT) { + HOOK_FOREACH(uc, hook, UC_HOOK_MEM_READ_PROT) + { if (hook->to_delete) continue; if (!HOOK_BOUND_CHECK(hook, paddr)) continue; tlb_hook_state_sync(env, retaddr, &hook_state); - JIT_CALLBACK_GUARD_VAR(handled, - ((uc_cb_eventmem_t)hook->callback)(uc, UC_MEM_READ_PROT, paddr, size, 0, hook->user_data)); + JIT_CALLBACK_GUARD_VAR( + handled, + ((uc_cb_eventmem_t)hook->callback)( + uc, UC_MEM_READ_PROT, paddr, size, 0, hook->user_data)); if (handled) break; // the last callback may already asked to stop emulation - if (uc->stop_request) + if (uc_stop_requested(uc)) break; } + if (tlb_exit_after_stopped_hook(env, retaddr)) { + goto load_exit; + } + if (handled) { uc->invalid_error = UC_ERR_OK; - tlb_revalidate_entry(env, mmu_idx, addr, size, access_type, - tlb_off, retaddr, &index, &entry, - &tlb_addr); - paddr = entry->paddr | (addr & ~TARGET_PAGE_MASK); - mr = uc->memory_mapping(uc, paddr); + tlb_revalidate_after_hook(env, &hook_state, mmu_idx, addr, size, + access_type, tlb_off, retaddr, &index, + &entry, &tlb_addr, &paddr, &mr); tlb_hook_state_restore(env, &hook_state); } else { uc->invalid_addr = paddr; uc->invalid_error = UC_ERR_READ_PROT; - // printf("***** Invalid memory read (non-readable) at " TARGET_FMT_lx "\n", addr); + // printf("***** Invalid memory read (non-readable) at " + // TARGET_FMT_lx "\n", addr); if (uc->nested_level > 0 && !uc->cpu->stopped) { cpu_exit(uc->cpu); // See comments above cpu_loop_exit_restore(uc->cpu, retaddr); } - return 0; + goto load_exit; } } } else if (code_read) { // code fetching // Unicorn: callback on fetch from NX - if (mr != NULL && !(mr->perms & UC_PROT_EXEC)) { // non-executable + if (mr != NULL && !(mr->perms & UC_PROT_EXEC)) { // non-executable handled = false; - HOOK_FOREACH(uc, hook, UC_HOOK_MEM_FETCH_PROT) { + HOOK_FOREACH(uc, hook, UC_HOOK_MEM_FETCH_PROT) + { if (hook->to_delete) continue; if (!HOOK_BOUND_CHECK(hook, paddr)) continue; tlb_hook_state_sync(env, retaddr, &hook_state); JIT_CALLBACK_GUARD_VAR(handled, - ((uc_cb_eventmem_t)hook->callback)(uc, UC_MEM_FETCH_PROT, paddr, size, 0, hook->user_data)); + ((uc_cb_eventmem_t)hook->callback)( + uc, UC_MEM_FETCH_PROT, paddr, size, + 0, hook->user_data)); if (handled) break; // the last callback may already asked to stop emulation - if (uc->stop_request) + if (uc_stop_requested(uc)) break; } + if (tlb_exit_after_stopped_hook(env, retaddr)) { + goto load_exit; + } + if (handled) { uc->invalid_error = UC_ERR_OK; - tlb_revalidate_entry(env, mmu_idx, addr, size, access_type, - tlb_off, retaddr, &index, &entry, - &tlb_addr); - paddr = entry->paddr | (addr & ~TARGET_PAGE_MASK); - mr = uc->memory_mapping(uc, paddr); + tlb_revalidate_after_hook(env, &hook_state, mmu_idx, addr, size, + access_type, tlb_off, retaddr, &index, + &entry, &tlb_addr, &paddr, &mr); tlb_hook_state_restore(env, &hook_state); } else { uc->invalid_addr = paddr; uc->invalid_error = UC_ERR_FETCH_PROT; - // printf("***** Invalid fetch (non-executable) at " TARGET_FMT_lx "\n", addr); + // printf("***** Invalid fetch (non-executable) at " + // TARGET_FMT_lx "\n", addr); if (uc->nested_level > 0 && !uc->cpu->stopped) { cpu_exit(uc->cpu); // See comments above cpu_loop_exit_restore(uc->cpu, retaddr); } - return 0; + goto load_exit; } } } /* Handle anything that isn't just a straight memory access. */ - if (unlikely(tlb_addr & ~TARGET_PAGE_MASK)) { + slow_flags = tlb_full_entry(env, mmu_idx, index)->slow_flags[access_type]; + if (likely(tlb_hook_only_ram_access(uc, mr, addr, size, tlb_addr, + slow_flags))) { + haddr = (void *)((uintptr_t)addr + entry->addend); + res = load_memop(haddr, op); + goto _out; + } + if (unlikely((tlb_addr & ~TARGET_PAGE_MASK) || slow_flags != 0)) { CPUTLBEntryFull *iotlbentry; bool need_swap; @@ -1953,16 +2259,16 @@ load_helper(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi, /* Handle watchpoints. */ if (unlikely(tlb_addr & TLB_WATCHPOINT)) { /* On watchpoint hit, this will longjmp out. */ - cpu_check_watchpoint(env_cpu(env), addr, size, - iotlbentry->attrs, BP_MEM_READ, retaddr); + cpu_check_watchpoint(env_cpu(env), addr, size, iotlbentry->attrs, + BP_MEM_READ, retaddr); } - need_swap = size > 1 && (tlb_addr & TLB_BSWAP); + need_swap = size > 1 && (slow_flags & TLB_BSWAP); /* Handle I/O access. */ if (likely(tlb_addr & TLB_MMIO)) { - res = io_readx(env, iotlbentry, mmu_idx, addr, retaddr, - access_type, op ^ (need_swap * MO_BSWAP)); + res = io_readx(env, iotlbentry, mmu_idx, addr, retaddr, access_type, + op ^ (need_swap * MO_BSWAP)); goto _out; } @@ -1982,9 +2288,8 @@ load_helper(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi, } /* Handle slow unaligned access (it spans two pages or IO). */ - if (size > 1 - && unlikely((addr & ~TARGET_PAGE_MASK) + size - 1 - >= TARGET_PAGE_SIZE)) { + if (size > 1 && + unlikely((addr & ~TARGET_PAGE_MASK) + size - 1 >= TARGET_PAGE_SIZE)) { target_ulong addr1, addr2; uint64_t r1, r2; unsigned shift; @@ -2016,22 +2321,30 @@ load_helper(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi, _out: // Unicorn: callback on successful data read if (!code_read) { - if (!uc->size_recur_mem) { // disabling read callback if in recursive call - HOOK_FOREACH(uc, hook, UC_HOOK_MEM_READ_AFTER) { + if (!uc->size_recur_mem) { // disabling read callback if in recursive + // call + cur = hook_dispatch_first_match(uc, UC_HOOK_MEM_READ_AFTER_IDX, + paddr); + for (; cur != NULL && (hook = (struct hook *)cur->data); + cur = cur->next) { if (hook->to_delete) continue; if (!HOOK_BOUND_CHECK(hook, paddr)) continue; tlb_hook_state_sync(env, retaddr, &hook_state); - JIT_CALLBACK_GUARD(((uc_cb_hookmem_t)hook->callback)(env->uc, UC_MEM_READ_AFTER, paddr, size, res, hook->user_data)); + JIT_CALLBACK_GUARD(((uc_cb_hookmem_t)hook->callback)( + env->uc, UC_MEM_READ_AFTER, paddr, size, res, + hook->user_data)); // the last callback may already asked to stop emulation - if (uc->stop_request) + if (uc_stop_requested(uc)) break; } } tlb_hook_state_restore(env, &hook_state); } +load_exit: + cputlb_exit_request(env, oi, retaddr); return res; } @@ -2109,15 +2422,15 @@ tcg_target_ulong helper_be_ldul_mmu(CPUArchState *env, target_ulong addr, return full_be_ldul_mmu(env, addr, oi, retaddr); } -uint64_t helper_le_ldq_mmu(CPUArchState *env, target_ulong addr, - TCGMemOpIdx oi, uintptr_t retaddr) +uint64_t helper_le_ldq_mmu(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi, + uintptr_t retaddr) { return load_helper(env, addr, oi, retaddr, MO_LEQ, false, helper_le_ldq_mmu); } -uint64_t helper_be_ldq_mmu(CPUArchState *env, target_ulong addr, - TCGMemOpIdx oi, uintptr_t retaddr) +uint64_t helper_be_ldq_mmu(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi, + uintptr_t retaddr) { return load_helper(env, addr, oi, retaddr, MO_BEQ, false, helper_be_ldq_mmu); @@ -2128,7 +2441,6 @@ uint64_t helper_be_ldq_mmu(CPUArchState *env, target_ulong addr, * avoid this for 64-bit data, or for 32-bit data on 32-bit host. */ - tcg_target_ulong helper_ret_ldsb_mmu(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi, uintptr_t retaddr) { @@ -2164,8 +2476,8 @@ tcg_target_ulong helper_be_ldsl_mmu(CPUArchState *env, target_ulong addr, */ static inline uint64_t cpu_load_helper(CPUArchState *env, abi_ptr addr, - int mmu_idx, uintptr_t retaddr, - MemOp op, FullLoadHelper *full_load) // qq + int mmu_idx, uintptr_t retaddr, MemOp op, + FullLoadHelper *full_load) // qq { TCGMemOpIdx oi; uint64_t ret; @@ -2177,49 +2489,49 @@ static inline uint64_t cpu_load_helper(CPUArchState *env, abi_ptr addr, return ret; } -uint32_t cpu_ldub_mmuidx_ra(CPUArchState *env, abi_ptr addr, - int mmu_idx, uintptr_t ra) +uint32_t cpu_ldub_mmuidx_ra(CPUArchState *env, abi_ptr addr, int mmu_idx, + uintptr_t ra) { return cpu_load_helper(env, addr, mmu_idx, ra, MO_UB, full_ldub_mmu); } -int cpu_ldsb_mmuidx_ra(CPUArchState *env, abi_ptr addr, - int mmu_idx, uintptr_t ra) +int cpu_ldsb_mmuidx_ra(CPUArchState *env, abi_ptr addr, int mmu_idx, + uintptr_t ra) { return (int8_t)cpu_load_helper(env, addr, mmu_idx, ra, MO_SB, full_ldub_mmu); } -uint32_t cpu_lduw_mmuidx_ra(CPUArchState *env, abi_ptr addr, - int mmu_idx, uintptr_t ra) +uint32_t cpu_lduw_mmuidx_ra(CPUArchState *env, abi_ptr addr, int mmu_idx, + uintptr_t ra) { return cpu_load_helper(env, addr, mmu_idx, ra, MO_TEUW, - MO_TE == MO_LE - ? full_le_lduw_mmu : full_be_lduw_mmu); + MO_TE == MO_LE ? full_le_lduw_mmu + : full_be_lduw_mmu); } -int cpu_ldsw_mmuidx_ra(CPUArchState *env, abi_ptr addr, - int mmu_idx, uintptr_t ra) +int cpu_ldsw_mmuidx_ra(CPUArchState *env, abi_ptr addr, int mmu_idx, + uintptr_t ra) { return (int16_t)cpu_load_helper(env, addr, mmu_idx, ra, MO_TESW, - MO_TE == MO_LE - ? full_le_lduw_mmu : full_be_lduw_mmu); + MO_TE == MO_LE ? full_le_lduw_mmu + : full_be_lduw_mmu); } -uint32_t cpu_ldl_mmuidx_ra(CPUArchState *env, abi_ptr addr, - int mmu_idx, uintptr_t ra) +uint32_t cpu_ldl_mmuidx_ra(CPUArchState *env, abi_ptr addr, int mmu_idx, + uintptr_t ra) { return cpu_load_helper(env, addr, mmu_idx, ra, MO_TEUL, - MO_TE == MO_LE - ? full_le_ldul_mmu : full_be_ldul_mmu); + MO_TE == MO_LE ? full_le_ldul_mmu + : full_be_ldul_mmu); } -uint64_t cpu_ldq_mmuidx_ra(CPUArchState *env, abi_ptr addr, - int mmu_idx, uintptr_t ra) +uint64_t cpu_ldq_mmuidx_ra(CPUArchState *env, abi_ptr addr, int mmu_idx, + uintptr_t ra) { return cpu_load_helper(env, addr, mmu_idx, ra, MO_TEQ, - MO_TE == MO_LE - ? helper_le_ldq_mmu : helper_be_ldq_mmu); + MO_TE == MO_LE ? helper_le_ldq_mmu + : helper_be_ldq_mmu); } uint32_t cpu_ldub_data_ra(CPUArchState *env, target_ulong ptr, @@ -2288,8 +2600,7 @@ uint64_t cpu_ldq_data(CPUArchState *env, target_ulong ptr) * Store Helpers */ -static inline void -store_memop(void *haddr, uint64_t val, MemOp op) +static inline void store_memop(void *haddr, uint64_t val, MemOp op) { switch (op) { case MO_UB: @@ -2319,9 +2630,9 @@ store_memop(void *haddr, uint64_t val, MemOp op) } } -static inline void -store_helper(CPUArchState *env, target_ulong addr, uint64_t val, - TCGMemOpIdx oi, uintptr_t retaddr, MemOp op) +static inline void store_helper(CPUArchState *env, target_ulong addr, + uint64_t val, TCGMemOpIdx oi, uintptr_t retaddr, + MemOp op) { struct uc_struct *uc = env->uc; HOOK_FOREACH_VAR_DECLARE; @@ -2337,22 +2648,23 @@ store_helper(CPUArchState *env, target_ulong addr, uint64_t val, struct hook *hook; bool handled; MemoryRegion *mr; + unsigned slow_flags; CPUTLBHookState hook_state; tlb_hook_state_init(env, &hook_state); /* Handle CPU specific unaligned behaviour */ if (addr & ((1 << a_bits) - 1)) { - cpu_tcg_unaligned_access(env_cpu(env), addr, MMU_DATA_STORE, - mmu_idx, retaddr); + cpu_tcg_unaligned_access(env_cpu(env), addr, MMU_DATA_STORE, mmu_idx, + retaddr); } /* If the TLB entry is for a different page, reload and try again. */ if (!tlb_hit(env->uc, tlb_addr, addr)) { if (!victim_tlb_hit(env, mmu_idx, index, tlb_off, - addr & TARGET_PAGE_MASK)) { - tlb_fill(env_cpu(env), addr, size, MMU_DATA_STORE, - mmu_idx, retaddr); + addr & TARGET_PAGE_MASK)) { + tlb_fill(env_cpu(env), addr, size, MMU_DATA_STORE, mmu_idx, + retaddr); index = tlb_index(env, mmu_idx, addr); entry = tlb_entry(env, mmu_idx, addr); } @@ -2360,126 +2672,153 @@ store_helper(CPUArchState *env, target_ulong addr, uint64_t val, } // Load the latest memory mapping. - paddr = entry->paddr | (addr & ~TARGET_PAGE_MASK); - mr = uc->memory_mapping(uc, paddr); + paddr = tlb_phys_addr(env, mmu_idx, index, addr); + mr = tlb_full_memory_region(env_cpu(env), + tlb_full_entry(env, mmu_idx, index)); if (!uc->size_recur_mem) { // disabling write callback if in recursive call // Unicorn: callback on memory write - HOOK_FOREACH(uc, hook, UC_HOOK_MEM_WRITE) { + cur = hook_dispatch_first_match(uc, UC_HOOK_MEM_WRITE_IDX, paddr); + for (; cur != NULL && (hook = (struct hook *)cur->data); + cur = cur->next) { if (hook->to_delete) continue; if (!HOOK_BOUND_CHECK(hook, paddr)) continue; tlb_hook_state_sync(env, retaddr, &hook_state); - JIT_CALLBACK_GUARD(((uc_cb_hookmem_t)hook->callback)(uc, UC_MEM_WRITE, paddr, size, val, hook->user_data)); + JIT_CALLBACK_GUARD(((uc_cb_hookmem_t)hook->callback)( + uc, UC_MEM_WRITE, paddr, size, val, hook->user_data)); // the last callback may already asked to stop emulation - if (uc->stop_request) + if (uc_stop_requested(uc)) break; } + if (tlb_exit_after_stopped_hook(env, retaddr)) { + goto store_exit; + } tlb_hook_state_restore(env, &hook_state); - tlb_revalidate_entry(env, mmu_idx, addr, size, MMU_DATA_STORE, - tlb_off, retaddr, &index, &entry, &tlb_addr); - paddr = entry->paddr | (addr & ~TARGET_PAGE_MASK); - mr = uc->memory_mapping(uc, paddr); + tlb_revalidate_after_hook(env, &hook_state, mmu_idx, addr, size, + MMU_DATA_STORE, tlb_off, retaddr, &index, + &entry, &tlb_addr, &paddr, &mr); } // Unicorn: callback on invalid memory if (mr == NULL) { handled = false; - HOOK_FOREACH(uc, hook, UC_HOOK_MEM_WRITE_UNMAPPED) { + HOOK_FOREACH(uc, hook, UC_HOOK_MEM_WRITE_UNMAPPED) + { if (hook->to_delete) continue; if (!HOOK_BOUND_CHECK(hook, paddr)) continue; tlb_hook_state_sync(env, retaddr, &hook_state); JIT_CALLBACK_GUARD_VAR(handled, - ((uc_cb_eventmem_t)hook->callback)(uc, UC_MEM_WRITE_UNMAPPED, paddr, size, val, hook->user_data)); + ((uc_cb_eventmem_t)hook->callback)( + uc, UC_MEM_WRITE_UNMAPPED, paddr, size, + val, hook->user_data)); if (handled) break; // the last callback may already asked to stop emulation - if (uc->stop_request) + if (uc_stop_requested(uc)) break; } + if (tlb_exit_after_stopped_hook(env, retaddr)) { + goto store_exit; + } + if (!handled) { // save error & quit uc->invalid_addr = paddr; uc->invalid_error = UC_ERR_WRITE_UNMAPPED; - // printf("***** Invalid memory write at " TARGET_FMT_lx "\n", addr); + // printf("***** Invalid memory write at " TARGET_FMT_lx "\n", + // addr); cpu_exit(uc->cpu); - return; + goto store_exit; } else { uc->invalid_error = UC_ERR_OK; - tlb_revalidate_entry(env, mmu_idx, addr, size, MMU_DATA_STORE, - tlb_off, retaddr, &index, &entry, - &tlb_addr); - paddr = entry->paddr | (addr & ~TARGET_PAGE_MASK); - mr = uc->memory_mapping(uc, paddr); + tlb_revalidate_after_hook(env, &hook_state, mmu_idx, addr, size, + MMU_DATA_STORE, tlb_off, retaddr, &index, + &entry, &tlb_addr, &paddr, &mr); if (mr == NULL) { uc->invalid_error = UC_ERR_MAP; cpu_exit(uc->cpu); - return; + goto store_exit; } tlb_hook_state_restore(env, &hook_state); } } // Unicorn: callback on non-writable memory - if (mr != NULL && !(mr->perms & UC_PROT_WRITE)) { //non-writable + if (mr != NULL && !(mr->perms & UC_PROT_WRITE)) { // non-writable // printf("not writable memory???\n"); handled = false; - HOOK_FOREACH(uc, hook, UC_HOOK_MEM_WRITE_PROT) { + HOOK_FOREACH(uc, hook, UC_HOOK_MEM_WRITE_PROT) + { if (hook->to_delete) continue; if (!HOOK_BOUND_CHECK(hook, paddr)) continue; tlb_hook_state_sync(env, retaddr, &hook_state); - JIT_CALLBACK_GUARD_VAR(handled, - ((uc_cb_eventmem_t)hook->callback)(uc, UC_MEM_WRITE_PROT, paddr, size, val, hook->user_data)); + JIT_CALLBACK_GUARD_VAR(handled, ((uc_cb_eventmem_t)hook->callback)( + uc, UC_MEM_WRITE_PROT, paddr, + size, val, hook->user_data)); if (handled) break; // the last callback may already asked to stop emulation - if (uc->stop_request) + if (uc_stop_requested(uc)) break; } + if (tlb_exit_after_stopped_hook(env, retaddr)) { + goto store_exit; + } + if (handled) { - tlb_revalidate_entry(env, mmu_idx, addr, size, MMU_DATA_STORE, - tlb_off, retaddr, &index, &entry, - &tlb_addr); - paddr = entry->paddr | (addr & ~TARGET_PAGE_MASK); - mr = uc->memory_mapping(uc, paddr); + tlb_revalidate_after_hook(env, &hook_state, mmu_idx, addr, size, + MMU_DATA_STORE, tlb_off, retaddr, &index, + &entry, &tlb_addr, &paddr, &mr); uc->invalid_error = UC_ERR_OK; tlb_hook_state_restore(env, &hook_state); } else { uc->invalid_addr = paddr; uc->invalid_error = UC_ERR_WRITE_PROT; - // printf("***** Invalid memory write (ro) at " TARGET_FMT_lx "\n", addr); + // printf("***** Invalid memory write (ro) at " TARGET_FMT_lx "\n", + // addr); cpu_exit(uc->cpu); - return; + goto store_exit; } } - if (uc->snapshot_level && mr->ram && mr->priority < uc->snapshot_level) { - mr = memory_cow(uc, mr, paddr & TARGET_PAGE_MASK, TARGET_PAGE_SIZE); + if (uc->memory_context_count != 0 && mr->ram && + mr->priority < uc->snapshot_level) { + mr = uc->memory_cow(uc, mr->uc_mapping, mr, + paddr & ~((hwaddr)TARGET_PAGE_SIZE - 1), + TARGET_PAGE_SIZE); if (!mr) { uc->invalid_addr = paddr; uc->invalid_error = UC_ERR_NOMEM; cpu_exit(uc->cpu); - return; + goto store_exit; } /* refill tlb after CoW */ - tlb_fill(env_cpu(env), addr, size, MMU_DATA_STORE, - mmu_idx, retaddr); + tlb_fill(env_cpu(env), addr, size, MMU_DATA_STORE, mmu_idx, retaddr); index = tlb_index(env, mmu_idx, addr); entry = tlb_entry(env, mmu_idx, addr); tlb_addr = tlb_addr_write(entry); } /* Handle anything that isn't just a straight memory access. */ - if (unlikely(tlb_addr & ~TARGET_PAGE_MASK)) { + slow_flags = + tlb_full_entry(env, mmu_idx, index)->slow_flags[MMU_DATA_STORE]; + if (likely(tlb_hook_only_ram_access(uc, mr, addr, size, tlb_addr, + slow_flags))) { + haddr = (void *)((uintptr_t)addr + entry->addend); + store_memop(haddr, val, op); + goto store_exit; + } + if (unlikely((tlb_addr & ~TARGET_PAGE_MASK) || slow_flags != 0)) { CPUTLBEntryFull *iotlbentry; bool need_swap; @@ -2493,27 +2832,28 @@ store_helper(CPUArchState *env, target_ulong addr, uint64_t val, /* Handle watchpoints. */ if (unlikely(tlb_addr & TLB_WATCHPOINT)) { /* On watchpoint hit, this will longjmp out. */ - cpu_check_watchpoint(env_cpu(env), addr, size, - iotlbentry->attrs, BP_MEM_WRITE, retaddr); + cpu_check_watchpoint(env_cpu(env), addr, size, iotlbentry->attrs, + BP_MEM_WRITE, retaddr); } - need_swap = size > 1 && (tlb_addr & TLB_BSWAP); + need_swap = size > 1 && (slow_flags & TLB_BSWAP); /* Handle I/O access. */ if (tlb_addr & TLB_MMIO) { io_writex(env, iotlbentry, mmu_idx, val, addr, retaddr, op ^ (need_swap * MO_BSWAP)); - return; + goto store_exit; } /* Ignore writes to ROM. */ if (unlikely(tlb_addr & TLB_DISCARD_WRITE)) { - return; + goto store_exit; } /* Handle clean RAM pages. */ if (tlb_addr & TLB_NOTDIRTY) { - notdirty_write(env_cpu(env), addr, size, iotlbentry, retaddr, entry); + notdirty_write(env_cpu(env), addr, size, iotlbentry, retaddr, + entry); } haddr = (void *)((uintptr_t)addr + entry->addend); @@ -2528,18 +2868,18 @@ store_helper(CPUArchState *env, target_ulong addr, uint64_t val, } else { store_memop(haddr, val, op); } - return; + goto store_exit; } /* Handle slow unaligned access (it spans two pages or IO). */ - if (size > 1 - && unlikely((addr & ~TARGET_PAGE_MASK) + size - 1 - >= TARGET_PAGE_SIZE)) { + if (size > 1 && + unlikely((addr & ~TARGET_PAGE_MASK) + size - 1 >= TARGET_PAGE_SIZE)) { int i; uintptr_t index2; CPUTLBEntry *entry2; target_ulong page2, tlb_addr2; size_t size2; + uint64_t table_epoch; int old_size; do_unaligned_access: @@ -2550,17 +2890,27 @@ store_helper(CPUArchState *env, target_ulong addr, uint64_t val, */ page2 = (addr + size) & TARGET_PAGE_MASK; size2 = (addr + size) & ~TARGET_PAGE_MASK; - index2 = tlb_index(env, mmu_idx, page2); - entry2 = tlb_entry(env, mmu_idx, page2); - tlb_addr2 = tlb_addr_write(entry2); - if (!tlb_hit_page(uc, tlb_addr2, page2)) { - if (!victim_tlb_hit(env, mmu_idx, index2, tlb_off, page2)) { - tlb_fill(env_cpu(env), page2, size2, MMU_DATA_STORE, - mmu_idx, retaddr); - index2 = tlb_index(env, mmu_idx, page2); - entry2 = tlb_entry(env, mmu_idx, page2); - } + for (;;) { + tlb_revalidate_entry(env, mmu_idx, addr, size - size2, + MMU_DATA_STORE, tlb_off, retaddr, &index, + &entry, &tlb_addr); + table_epoch = env_tlb(env)->c.table_epoch; + + index2 = tlb_index(env, mmu_idx, page2); + entry2 = tlb_entry(env, mmu_idx, page2); tlb_addr2 = tlb_addr_write(entry2); + if (!tlb_hit_page(uc, tlb_addr2, page2)) { + if (!victim_tlb_hit(env, mmu_idx, index2, tlb_off, page2)) { + tlb_fill(env_cpu(env), page2, size2, MMU_DATA_STORE, + mmu_idx, retaddr); + index2 = tlb_index(env, mmu_idx, page2); + entry2 = tlb_entry(env, mmu_idx, page2); + } + tlb_addr2 = tlb_addr_write(entry2); + } + if (table_epoch == env_tlb(env)->c.table_epoch) { + break; + } } /* @@ -2597,11 +2947,14 @@ store_helper(CPUArchState *env, target_ulong addr, uint64_t val, helper_ret_stb_mmu(env, addr + i, val8, oi, retaddr); } uc->size_recur_mem = old_size; - return; + goto store_exit; } haddr = (void *)((uintptr_t)addr + entry->addend); store_memop(haddr, val, op); + +store_exit: + cputlb_exit_request(env, oi, retaddr); } void helper_ret_stb_mmu(CPUArchState *env, target_ulong addr, uint8_t val, @@ -2650,9 +3003,9 @@ void helper_be_stq_mmu(CPUArchState *env, target_ulong addr, uint64_t val, * Store Helpers for cpu_ldst.h */ -static inline void -cpu_store_helper(CPUArchState *env, target_ulong addr, uint64_t val, - int mmu_idx, uintptr_t retaddr, MemOp op) // qq +static inline void cpu_store_helper(CPUArchState *env, target_ulong addr, + uint64_t val, int mmu_idx, + uintptr_t retaddr, MemOp op) // qq { TCGMemOpIdx oi; @@ -2684,26 +3037,26 @@ void cpu_stq_mmuidx_ra(CPUArchState *env, target_ulong addr, uint64_t val, cpu_store_helper(env, addr, val, mmu_idx, retaddr, MO_TEQ); } -void cpu_stb_data_ra(CPUArchState *env, target_ulong ptr, - uint32_t val, uintptr_t retaddr) +void cpu_stb_data_ra(CPUArchState *env, target_ulong ptr, uint32_t val, + uintptr_t retaddr) { cpu_stb_mmuidx_ra(env, ptr, val, cpu_mmu_index(env, false), retaddr); } -void cpu_stw_data_ra(CPUArchState *env, target_ulong ptr, - uint32_t val, uintptr_t retaddr) +void cpu_stw_data_ra(CPUArchState *env, target_ulong ptr, uint32_t val, + uintptr_t retaddr) { cpu_stw_mmuidx_ra(env, ptr, val, cpu_mmu_index(env, false), retaddr); } -void cpu_stl_data_ra(CPUArchState *env, target_ulong ptr, - uint32_t val, uintptr_t retaddr) +void cpu_stl_data_ra(CPUArchState *env, target_ulong ptr, uint32_t val, + uintptr_t retaddr) { cpu_stl_mmuidx_ra(env, ptr, val, cpu_mmu_index(env, false), retaddr); } -void cpu_stq_data_ra(CPUArchState *env, target_ulong ptr, - uint64_t val, uintptr_t retaddr) +void cpu_stq_data_ra(CPUArchState *env, target_ulong ptr, uint64_t val, + uintptr_t retaddr) { cpu_stq_mmuidx_ra(env, ptr, val, cpu_mmu_index(env, false), retaddr); } @@ -2731,13 +3084,12 @@ void cpu_stq_data(CPUArchState *env, target_ulong ptr, uint64_t val) /* First set of helpers allows passing in of OI and RETADDR. This makes them callable from other helpers. */ -#define EXTRA_ARGS , TCGMemOpIdx oi, uintptr_t retaddr -#define ATOMIC_NAME(X) \ - HELPER(glue(glue(glue(atomic_ ## X, SUFFIX), END), _mmu)) +#define EXTRA_ARGS , TCGMemOpIdx oi, uintptr_t retaddr +#define ATOMIC_NAME(X) HELPER(glue(glue(glue(atomic_##X, SUFFIX), END), _mmu)) #define ATOMIC_MMU_DECLS #define ATOMIC_MMU_LOOKUP atomic_mmu_lookup(env, addr, oi, retaddr) #define ATOMIC_MMU_CLEANUP -#define ATOMIC_MMU_IDX get_mmuidx(oi) +#define ATOMIC_MMU_IDX get_mmuidx(oi) #define DATA_SIZE 1 #include "atomic_template.h" @@ -2763,9 +3115,9 @@ void cpu_stq_data(CPUArchState *env, target_ulong ptr, uint64_t val) #undef EXTRA_ARGS #undef ATOMIC_NAME #undef ATOMIC_MMU_LOOKUP -#define EXTRA_ARGS , TCGMemOpIdx oi -#define ATOMIC_NAME(X) HELPER(glue(glue(atomic_ ## X, SUFFIX), END)) -#define ATOMIC_MMU_LOOKUP atomic_mmu_lookup(env, addr, oi, GETPC()) +#define EXTRA_ARGS , TCGMemOpIdx oi +#define ATOMIC_NAME(X) HELPER(glue(glue(atomic_##X, SUFFIX), END)) +#define ATOMIC_MMU_LOOKUP atomic_mmu_lookup(env, addr, oi, GETPC()) #define DATA_SIZE 1 #include "atomic_template.h" diff --git a/qemu/accel/tcg/tb-exec-frame.h b/qemu/accel/tcg/tb-exec-frame.h new file mode 100644 index 0000000000..10d8176a76 --- /dev/null +++ b/qemu/accel/tcg/tb-exec-frame.h @@ -0,0 +1,81 @@ +/* + * Active translation block execution tracking. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef ACCEL_TCG_TB_EXEC_FRAME_H +#define ACCEL_TCG_TB_EXEC_FRAME_H + +static inline void tb_exec_frame_set_tb(UcTbExecFrame *frame, + TranslationBlock *tb) +{ + frame->tb = tb; +} + +static inline void tb_exec_frame_clear(UcTbExecFrame *frame) +{ + frame->tb = NULL; +} + +static inline bool tb_exec_frame_resolve(uc_engine *uc, + const TranslationBlock *tb, + uint64_t phys_start[2], + uint32_t phys_size[2]) +{ + uint32_t page_offset; + uint32_t first_size; + + phys_start[0] = 0; + phys_start[1] = 0; + phys_size[0] = 0; + phys_size[1] = 0; + + /* Non-RAM CF_NOCACHE TBs have no physical fragment. */ + if (tb == NULL || tb->page_addr[0] == -1) { + return false; + } + + page_offset = tb->pc & uc->target_page_align; + first_size = MIN((uint32_t)tb->size, + uc->target_page_size - page_offset); + phys_start[0] = tb->page_addr[0] + page_offset; + phys_size[0] = first_size; + if (tb->page_addr[1] != -1) { + phys_start[1] = tb->page_addr[1]; + phys_size[1] = tb->size - first_size; + } + return true; +} + +static inline bool tb_exec_frame_contains_retaddr(const TranslationBlock *tb, + uintptr_t retaddr) +{ + uintptr_t start; + + if (tb == NULL || retaddr == 0) { + return false; + } + start = (uintptr_t)tb->tc.ptr; + return retaddr >= start && retaddr - start < tb->tc.size; +} + +static inline bool tb_exec_frame_publish_retaddr(uc_engine *uc, + uintptr_t retaddr) +{ + UcTbExecFrame *frame = uc->active_tb_exec_frame; + TranslationBlock *tb; + + if (frame == NULL || !frame->active) { + return true; + } + if (tb_exec_frame_contains_retaddr(frame->tb, retaddr)) { + return true; + } + + tb = retaddr != 0 ? tcg_tb_lookup(uc->tcg_ctx, retaddr) : NULL; + frame->tb = tb; + return tb != NULL; +} + +#endif /* ACCEL_TCG_TB_EXEC_FRAME_H */ diff --git a/qemu/accel/tcg/tb-maint.c b/qemu/accel/tcg/tb-maint.c index 44495f49bb..85d333d977 100644 --- a/qemu/accel/tcg/tb-maint.c +++ b/qemu/accel/tcg/tb-maint.c @@ -226,6 +226,7 @@ static void do_tb_flush(CPUState *cpu, run_on_cpu_data tb_flush_count) CODE_GEN_HTABLE_SIZE); page_flush_tb(cpu->uc); + cpu->uc->last_tb_valid = false; tcg_region_reset_all(cpu->uc->tcg_ctx); /* XXX: flush processor icache at this point if cache flush is expensive */ @@ -339,6 +340,45 @@ static inline void tb_jmp_unlink(TranslationBlock *dest) dest->jmp_list_head = (uintptr_t)NULL; } +static gboolean tb_flush_jit_unlink(gpointer key, gpointer value, + gpointer data) +{ + TranslationBlock *tb = value; + + (void)key; + (void)data; + if (tb->cflags & CF_INVALID) { + return false; + } + tb->cflags |= CF_INVALID; + tb_remove_from_jmp_list(tb, 0); + tb_remove_from_jmp_list(tb, 1); + tb_jmp_unlink(tb); + return false; +} + +void tb_flush_jit(CPUState *cpu) +{ + struct uc_struct *uc = cpu->uc; + TCGContext *tcg_ctx = uc->tcg_ctx; + bool code_gen_locked; + + mmap_lock(); + code_gen_locked = tb_exec_is_locked(uc); + tb_exec_unlock(uc); + + g_tree_foreach(tcg_ctx->tree, tb_flush_jit_unlink, NULL); + tcg_restore_state_cache_clear(tcg_ctx); + cpu_tb_jmp_cache_clear(cpu); + qht_reset_size(uc, &tcg_ctx->tb_ctx.htable, CODE_GEN_HTABLE_SIZE); + page_flush_tb(uc); + uc->last_tb_valid = false; + tcg_ctx->tb_ctx.tb_flush_count++; + + tb_exec_change(uc, code_gen_locked); + mmap_unlock(); +} + /* * If @rm_from_page_list is set, call with logical ownership of the TB pages. */ @@ -359,6 +399,7 @@ static void do_tb_phys_invalidate(TCGContext *tcg_ctx, /* make sure no further incoming jumps will be chained to this TB */ tb->cflags = tb->cflags | CF_INVALID; + tcg_restore_state_cache_remove(tcg_ctx, tb); /* remove the TB from the hash list */ phys_pc = tb->page_addr[0] + (tb->pc & ~TARGET_PAGE_MASK); @@ -634,7 +675,7 @@ static void tb_invalidate_phys_page_range__locked( if (current_tb_modified) { page_collection_unlock(pages); /* Force execution of one insn next time. */ - cpu->cflags_next_tb = 1 | curr_cflags(); + cpu->cflags_next_tb = 1 | curr_cflags(cpu->uc); mmap_unlock(); cpu_loop_exit_noexc(cpu); } diff --git a/qemu/accel/tcg/tcg-runtime.c b/qemu/accel/tcg/tcg-runtime.c index 216ea89cc5..57e3c8608a 100644 --- a/qemu/accel/tcg/tcg-runtime.c +++ b/qemu/accel/tcg/tcg-runtime.c @@ -28,9 +28,12 @@ #include "exec/cpu_ldst.h" #include "exec/exec-all.h" #include "exec/tb-lookup.h" +#include "hw/core/tcg-cpu-ops.h" #include "tcg/tcg.h" +#include "tcg/tcg-apple-jit.h" #include +#include "tb-exec-frame.h" /* 32-bit helpers */ @@ -153,7 +156,8 @@ void *HELPER(lookup_tb_ptr)(CPUArchState *env) uint32_t flags; struct uc_struct *uc = (struct uc_struct *)cpu->uc; - tb = tb_lookup__cpu_state(cpu, &pc, &cs_base, &flags, curr_cflags()); + tb = tb_lookup__cpu_state(cpu, &pc, &cs_base, &flags, + curr_cflags(cpu->uc)); if (tb == NULL) { return uc->tcg_ctx->code_gen_epilogue; } @@ -169,7 +173,7 @@ void HELPER(emu_stop)(void *p) { uc_engine *uc = p; - uc->stop_request = true; + uc_set_stop_request(uc, true); break_translation_loop(uc); } @@ -178,24 +182,102 @@ void HELPER(exit_atomic)(CPUArchState *env) cpu_loop_exit_atomic(env_cpu(env), GETPC()); } -void HELPER(check_exit_request)(void *p, uint32_t in_delay_slot) { +void HELPER(uc_tracecode_single)(void *item, uint32_t size, void *handle, + uint64_t address) +{ + struct uc_struct *uc = handle; + struct list_item *cur = item; + struct hook *hook; + + if (size == 0) { + return; + } + if (!uc_stop_requested(uc)) { + for (; cur != NULL && (hook = (struct hook *)cur->data); + cur = cur->next) { + if (HOOK_BOUND_CHECK(hook, (uint64_t)address)) { + JIT_CALLBACK_GUARD(((uc_cb_hookcode_t)hook->callback)( + uc, address, size, hook->user_data)); + } + if (uc_stop_requested(uc)) { + break; + } + } + } + if (qatomic_read(&uc->cpu->exit_request_pending)) { + cpu_tcg_exit_request(uc, GETPC()); + } +} + +void QEMU_NORETURN cpu_tcg_exit_request(uc_engine *uc, uintptr_t retaddr) +{ + CPUState *cpu = uc->cpu; + uint32_t pending = qatomic_read(&cpu->exit_request_pending); + + g_assert(pending); + (void)pending; + + /* Keep the cpu_tb_exec teardown behavior on the slow exit path. */ + if (uc->nested_level == 1) { + tb_exec_unlock(uc); + } + if (uc->skip_sync_pc_on_exit) { + cpu_restore_icount(cpu, retaddr); + uc->skip_sync_pc_on_exit = false; + cpu_loop_exit(cpu); + } else { + cpu_loop_exit_restore(cpu, retaddr); + } +} + +void HELPER(exit_request)(void *p, uint32_t in_delay_slot) +{ uc_engine *uc = p; - if (cpu_loop_exit_requested(uc->cpu) && !in_delay_slot) { - // There are stil something we have to before exiting to be compatible with previous behaviors + g_assert(!in_delay_slot); + cpu_tcg_exit_request(uc, GETPC()); +} - // from cpu_tb_exec - if (uc->nested_level == 1) { - // Only unlock (allow writing to JIT area) if we are the outmost uc_emu_start - tb_exec_unlock(uc); - } - uc->cpu->tcg_exit_req = 0; +void HELPER(check_exit_request)(void *p, uint32_t in_delay_slot) +{ + uc_engine *uc = p; + + if (!in_delay_slot && qatomic_read(&uc->cpu->exit_request_pending)) { + cpu_tcg_exit_request(uc, GETPC()); + } +} + +void HELPER(check_counted_entry)(void *p, uint32_t in_delay_slot, + void *tb_ptr, uint32_t num_insns) +{ + uc_engine *uc = p; + CPUState *cpu = uc->cpu; + + if (!in_delay_slot && qatomic_read(&cpu->exit_request_pending)) { + cpu_tcg_exit_request(uc, GETPC()); + } + + if (num_insns != 0 && uc->emu_count != 0) { + size_t remaining = cpu->icount_decr_ptr->u16.low + uc->emu_counter; + + if (remaining >= num_insns) { + size_t chunk = MIN((size_t)UINT16_MAX, remaining); - if (uc->skip_sync_pc_on_exit) { - uc->skip_sync_pc_on_exit = false; - cpu_loop_exit(uc->cpu); + cpu->icount_decr_ptr->u16.low = chunk - num_insns; + uc->emu_counter = remaining - chunk; } else { - cpu_loop_exit_restore(uc->cpu, GETPC()); + cpu_tcg_synchronize_from_tb(cpu, (TranslationBlock *)tb_ptr); + cpu->icount_decr_ptr->u16.low = remaining; + uc->emu_counter = 0; + if (remaining == 0) { + uc_set_stop_request(uc, true); + } else { + cpu->cflags_next_tb = + CF_USE_ICOUNT | (uint32_t)remaining; + uc->quit_request = true; + } + cpu_exit(cpu); + cpu_loop_exit(cpu); } } } diff --git a/qemu/accel/tcg/tcg-runtime.h b/qemu/accel/tcg/tcg-runtime.h index 42df0fca09..1196cc95d7 100644 --- a/qemu/accel/tcg/tcg-runtime.h +++ b/qemu/accel/tcg/tcg-runtime.h @@ -263,4 +263,7 @@ DEF_HELPER_FLAGS_4(gvec_leu64, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) DEF_HELPER_FLAGS_5(gvec_bitsel, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_2(exit_request, TCG_CALL_NO_WG, noreturn, ptr, i32) DEF_HELPER_2(check_exit_request, void, ptr, i32) +DEF_HELPER_4(check_counted_entry, void, ptr, i32, ptr, i32) +DEF_HELPER_4(uc_tracecode_single, void, ptr, i32, ptr, i64) diff --git a/qemu/accel/tcg/translate-all.c b/qemu/accel/tcg/translate-all.c index 1fcbb2dce6..4c0d0a5133 100644 --- a/qemu/accel/tcg/translate-all.c +++ b/qemu/accel/tcg/translate-all.c @@ -207,15 +207,17 @@ static int encode_search(struct uc_struct *uc, TranslationBlock *tb, uint8_t *bl * When reset_icount is true, current TB will be interrupted and * icount should be recalculated. */ -int cpu_restore_state_from_tb(CPUState *cpu, TranslationBlock *tb, - uintptr_t searched_pc, bool reset_icount) +static int cpu_unwind_state_data_from_tb(TranslationBlock *tb, + uintptr_t searched_pc, + target_ulong *data) { - target_ulong data[TARGET_INSN_START_WORDS] = { tb->pc }; uintptr_t host_pc = (uintptr_t)tb->tc.ptr; - CPUArchState *env = cpu->env_ptr; uint8_t *p = (uint8_t *)tb->tc.ptr + tb->tc.size; int i, j, num_insns = tb->icount; + memset(data, 0, sizeof(*data) * TARGET_INSN_START_WORDS); + data[0] = tb->pc; + searched_pc -= GETPC_ADJ; if (searched_pc < host_pc) { @@ -230,25 +232,51 @@ int cpu_restore_state_from_tb(CPUState *cpu, TranslationBlock *tb, } host_pc += decode_sleb128(&p); if (host_pc > searched_pc) { - goto found; + return num_insns - i; } } return -1; +} + +static void cpu_restore_state_from_data(CPUState *cpu, TranslationBlock *tb, + target_ulong *data, int insns_left, + bool reset_icount) +{ + CPUArchState *env = cpu->env_ptr; - found: if (reset_icount && (tb_cflags(tb) & CF_USE_ICOUNT)) { /* Reset the cycle counter to the start of the block and shift if to the number of actually executed instructions */ - cpu_neg(cpu)->icount_decr.u16.low += num_insns - i; + cpu_neg(cpu)->icount_decr.u16.low += insns_left; } restore_state_to_opc(env, tb, data); +} + +int cpu_restore_state_from_tb(CPUState *cpu, TranslationBlock *tb, + uintptr_t searched_pc, bool reset_icount) +{ + target_ulong data[TARGET_INSN_START_WORDS]; + int insns_left; + + insns_left = cpu_unwind_state_data_from_tb(tb, searched_pc, data); + if (insns_left < 0) { + return -1; + } + cpu_restore_state_from_data(cpu, tb, data, insns_left, reset_icount); return 0; } +static inline unsigned restore_state_cache_index(uintptr_t host_pc) +{ + return ((host_pc >> 4) ^ (host_pc >> 12)) & + (TCG_RESTORE_STATE_CACHE_SIZE - 1); +} + bool cpu_restore_state(CPUState *cpu, uintptr_t host_pc, bool will_exit) { TCGContext *tcg_ctx = cpu->uc->tcg_ctx; + TCGRestoreStateCacheEntry *entry; TranslationBlock *tb; bool r = false; uintptr_t check_offset; @@ -271,9 +299,34 @@ bool cpu_restore_state(CPUState *cpu, uintptr_t host_pc, bool will_exit) (uintptr_t)uc->tcg_ctx->code_gen_buffer; if (check_offset < uc->tcg_ctx->code_gen_buffer_size) { + entry = &tcg_ctx->restore_state_cache[ + restore_state_cache_index(host_pc)]; + if (entry->host_pc == host_pc && entry->tb != NULL && + !(tb_cflags(entry->tb) & CF_INVALID)) { + target_ulong data[TARGET_INSN_START_WORDS]; + + memcpy(data, entry->data, sizeof(data)); + cpu_restore_state_from_data(cpu, entry->tb, data, + entry->insns_left, will_exit); + return true; + } + tb = tcg_tb_lookup(tcg_ctx, host_pc); if (tb) { - cpu_restore_state_from_tb(cpu, tb, host_pc, will_exit); + target_ulong data[TARGET_INSN_START_WORDS]; + int insns_left; + + insns_left = cpu_unwind_state_data_from_tb(tb, host_pc, data); + if (insns_left >= 0) { + cpu_restore_state_from_data(cpu, tb, data, insns_left, + will_exit); + if (!(tb_cflags(tb) & (CF_NOCACHE | CF_INVALID))) { + entry->host_pc = host_pc; + entry->tb = tb; + entry->insns_left = insns_left; + memcpy(entry->data, data, sizeof(entry->data)); + } + } if (tb_cflags(tb) & CF_NOCACHE) { /* one-shot translation, invalidate it immediately */ tb_phys_invalidate(tcg_ctx, tb, -1); @@ -286,6 +339,54 @@ bool cpu_restore_state(CPUState *cpu, uintptr_t host_pc, bool will_exit) return r; } +bool cpu_restore_icount(CPUState *cpu, uintptr_t host_pc) +{ + TCGContext *tcg_ctx = cpu->uc->tcg_ctx; + TCGRestoreStateCacheEntry *entry; + TranslationBlock *tb; + uintptr_t check_offset; + target_ulong data[TARGET_INSN_START_WORDS]; + int insns_left; + + check_offset = (uintptr_t)tcg_splitwx_to_rw((void *)host_pc) - + (uintptr_t)tcg_ctx->code_gen_buffer; + if (check_offset >= tcg_ctx->code_gen_buffer_size) { + return false; + } + + entry = &tcg_ctx->restore_state_cache[ + restore_state_cache_index(host_pc)]; + if (entry->host_pc == host_pc && entry->tb != NULL && + !(tb_cflags(entry->tb) & CF_INVALID)) { + if (tb_cflags(entry->tb) & CF_USE_ICOUNT) { + cpu_neg(cpu)->icount_decr.u16.low += entry->insns_left; + } + return true; + } + + tb = tcg_tb_lookup(tcg_ctx, host_pc); + if (tb == NULL) { + return false; + } + insns_left = cpu_unwind_state_data_from_tb(tb, host_pc, data); + if (insns_left < 0) { + return false; + } + if (tb_cflags(tb) & CF_USE_ICOUNT) { + cpu_neg(cpu)->icount_decr.u16.low += insns_left; + } + if (!(tb_cflags(tb) & (CF_NOCACHE | CF_INVALID))) { + entry->host_pc = host_pc; + entry->tb = tb; + entry->insns_left = insns_left; + memcpy(entry->data, data, sizeof(entry->data)); + } else { + tb_phys_invalidate(tcg_ctx, tb, -1); + tcg_tb_remove(tcg_ctx, tb); + } + return true; +} + static void page_init(struct uc_struct *uc) { page_size_init(uc); @@ -764,6 +865,9 @@ static inline void may_remove_handler(struct uc_struct *uc) { if (uc->seh_handle) { RemoveVectoredExceptionHandler(uc->seh_handle); } + if (uc->vch_handle) { + RemoveVectoredContinueHandler(uc->vch_handle); + } VirtualFree(uc->seh_closure, 0, MEM_RELEASE); } } @@ -805,9 +909,9 @@ static inline void *alloc_code_gen_buffer(struct uc_struct *uc) // ; rax = &data // mov [rax], rdx ; save rdx // mov rdx, [rax+0x8] ; move uc pointer to 2nd arg - // sub rsp, 0x10; reserve 2 slots as ms fastcall requires + // sub rsp, 0x28; align the stack and reserve Win64 shadow space // call [rax + 0x10] ; go to handler - const char tramp[] = "\x48\x89\x10\x48\x8b\x50\x08\x48\x83\xec\x10\xff\x50\x10"; + const char tramp[] = "\x48\x89\x10\x48\x8b\x50\x08\x48\x83\xec\x28\xff\x50\x10"; memcpy(ptr, (void*)tramp, sizeof(tramp) - 1); // Note last zero! ptr += sizeof(tramp) - 1; *ptr = 0x48; // REX.w @@ -817,10 +921,10 @@ static inline void *alloc_code_gen_buffer(struct uc_struct *uc) memcpy(ptr, &data, 8); // mov rdx, &data ptr += 8; // ; rdx = &data - // add rsp, 0x10 ; clean stack + // add rsp, 0x28 ; clean stack // mov rdx, [rdx] ; restore rdx // ret - const char tramp2[] = "\x48\x83\xc4\x10\x48\x8b\x12\xc3"; + const char tramp2[] = "\x48\x83\xc4\x28\x48\x8b\x12\xc3"; memcpy(ptr, (void*)tramp2, sizeof(tramp2) - 1); memcpy(data + 0x8, (void*)&uc, 8); @@ -861,6 +965,16 @@ static inline void *alloc_code_gen_buffer(struct uc_struct *uc) uc->seh_closure = NULL; return NULL; } + /* Stop later continue handlers after the range-checked VEH succeeds. */ + uc->vch_handle = AddVectoredContinueHandler( + 1, (PVECTORED_EXCEPTION_HANDLER)closure); + if (!uc->vch_handle) { + RemoveVectoredExceptionHandler(uc->seh_handle); + uc->seh_handle = NULL; + VirtualFree(uc->seh_closure, 0, MEM_RELEASE); + uc->seh_closure = NULL; + return NULL; + } return VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_EXECUTE_READWRITE); #else @@ -965,10 +1079,18 @@ static void uc_tb_flush(struct uc_struct *uc) { tb_exec_lock(uc); } +static void uc_tb_flush_deferred(struct uc_struct *uc) +{ + tb_exec_unlock(uc); + tb_flush_jit(uc->cpu); + tb_exec_lock(uc); +} + static void uc_invalidate_tb(struct uc_struct *uc, uint64_t start_addr, size_t len) { tb_page_addr_t start, end; + g_assert(uc->nested_level < UC_MAX_NESTED_JMP_LEVEL); uc->nested_level++; if (sigsetjmp(uc->jmp_bufs[uc->nested_level - 1], 0) != 0) { // We a get cpu fault in get_page_addr_code, ignore it. @@ -1009,7 +1131,7 @@ static uc_err uc_gen_tb(struct uc_struct *uc, uint64_t addr, uc_tb *out_tb) uint32_t cflags = cpu->cflags_next_tb; if (cflags == -1) { - cflags = curr_cflags(); + cflags = curr_cflags(uc); } cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags); @@ -1079,6 +1201,7 @@ void tcg_exec_init(struct uc_struct *uc, uint32_t tb_size) uc->uc_invalidate_tb = uc_invalidate_tb; uc->uc_gen_tb = uc_gen_tb; uc->tb_flush = uc_tb_flush; + uc->tb_flush_deferred = uc_tb_flush_deferred; /* Inline hooks optimization */ uc->add_inline_hook = uc_add_inline_hook; @@ -1130,6 +1253,14 @@ TranslationBlock *tb_gen_code(CPUState *cpu, tb = tcg_tb_alloc(tcg_ctx); if (unlikely(!tb)) { /* flush must be done */ + if (cpu->uc->tb_exec_depth != 0) { + cpu->uc->tb_flush_pending = true; + cpu->uc->invalid_error = UC_ERR_NOMEM; + mmap_unlock(); + tb_flush_jit(cpu); + cpu->exception_index = EXCP_INTERRUPT; + cpu_loop_exit(cpu); + } tb_flush(cpu); mmap_unlock(); /* Make the execution loop process the flush as soon as possible. */ @@ -1151,9 +1282,11 @@ TranslationBlock *tb_gen_code(CPUState *cpu, tcg_func_start(tcg_ctx); tcg_ctx->cpu = env_cpu(env); + tcg_ctx->gen_tb = tb; UC_TRACE_START(UC_TRACE_TB_TRANS); gen_intermediate_code(cpu, tb, max_insns); UC_TRACE_END(UC_TRACE_TB_TRANS, "[uc] translate tb 0x%" PRIx64 ": ", tb->pc); + tcg_ctx->gen_tb = NULL; tcg_ctx->cpu = NULL; /* generate machine code */ @@ -1331,7 +1464,7 @@ void cpu_io_recompile(CPUState *cpu, uintptr_t retaddr) #endif /* Generate a new TB executing the I/O insn. */ - cpu->cflags_next_tb = curr_cflags() | CF_LAST_IO | n; + cpu->cflags_next_tb = curr_cflags(cpu->uc) | CF_LAST_IO | n; if (tb_cflags(tb) & CF_NOCACHE) { if (tb->orig_tb) { diff --git a/qemu/accel/tcg/translator.c b/qemu/accel/tcg/translator.c index fbb5096873..b67f091a17 100644 --- a/qemu/accel/tcg/translator.c +++ b/qemu/accel/tcg/translator.c @@ -64,9 +64,10 @@ void translator_loop(const TranslatorOps *ops, DisasContextBase *db, /* Unicorn: early check to see if the address of this block is * the "run until" address. */ if (uc_addr_is_exit(uc, tb->pc)) { - TCGv_ptr puc = tcg_const_ptr(tcg_ctx, uc); + TCGv_ptr puc; gen_tb_start(tcg_ctx, db->tb); + puc = tcg_const_ptr(tcg_ctx, uc); ops->tb_start(db, cpu); db->num_insns++; ops->insn_start(db, cpu); diff --git a/qemu/arm.h b/qemu/arm.h index 97f2819ea1..9b3535bc52 100644 --- a/qemu/arm.h +++ b/qemu/arm.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _arm #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_arm #define gen_helper_check_exit_request gen_helper_check_exit_request_arm +#define gen_helper_exit_request gen_helper_exit_request_arm +#define helper_check_counted_entry helper_check_counted_entry_arm +#define helper_check_exit_request helper_check_exit_request_arm +#define helper_exit_request helper_exit_request_arm +#define helper_uc_tracecode_single helper_uc_tracecode_single_arm +#define cpu_restore_icount cpu_restore_icount_arm +#define cpu_tcg_exit_request cpu_tcg_exit_request_arm #define unicorn_fill_tlb unicorn_fill_tlb_arm #define reg_read reg_read_arm #define reg_write reg_write_arm @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_arm #define flatview_translate flatview_translate_arm #define flatview_copy flatview_copy_arm +#define flatview_reserve flatview_reserve_arm +#define address_space_restore_flatview address_space_restore_flatview_arm #define address_space_translate_for_iotlb address_space_translate_for_iotlb_arm #define qemu_get_cpu qemu_get_cpu_arm #define cpu_address_space_init cpu_address_space_init_arm @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_arm #define memory_moveout memory_moveout_arm #define memory_movein memory_movein_arm +#define memory_mapping_restore_topology memory_mapping_restore_topology_arm +#define memory_mapping_free memory_mapping_free_arm +#define memory_mapping_prune memory_mapping_prune_arm +#define memory_mapping_normalize memory_mapping_normalize_arm #define memory_free memory_free_arm #define flatview_unref flatview_unref_arm #define address_space_get_flatview address_space_get_flatview_arm @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_arm #define tb_cleanup tb_cleanup_arm #define tb_flush tb_flush_arm +#define tb_flush_jit tb_flush_jit_arm #define tb_phys_invalidate tb_phys_invalidate_arm #define tb_gen_code tb_gen_code_arm #define tb_reset_jump tb_reset_jump_arm diff --git a/qemu/exec.c b/qemu/exec.c index f53d3db35e..aa40f23d56 100644 --- a/qemu/exec.c +++ b/qemu/exec.c @@ -1361,7 +1361,7 @@ void cpu_check_watchpoint(CPUState *cpu, vaddr addr, vaddr len, cpu_loop_exit(cpu); } else { /* Force execution of one instruction next time. */ - cpu->cflags_next_tb = 1 | curr_cflags(); + cpu->cflags_next_tb = 1 | curr_cflags(cpu->uc); mmap_unlock(); cpu_loop_exit_noexc(cpu); } diff --git a/qemu/hw/core/cpu.c b/qemu/hw/core/cpu.c index 18f8bfa69f..597c1cc4f6 100644 --- a/qemu/hw/core/cpu.c +++ b/qemu/hw/core/cpu.c @@ -20,6 +20,7 @@ #include "uc_priv.h" #include "qemu/osdep.h" +#include "qemu/atomic.h" #include "hw/core/cpu.h" #include "sysemu/tcg.h" @@ -57,9 +58,8 @@ void cpu_reset_interrupt(CPUState *cpu, int mask) void cpu_exit(CPUState *cpu) { - cpu->exit_request = 1; - cpu->tcg_exit_req = 1; - cpu->icount_decr_ptr->u16.high = -1; + qatomic_set(&cpu->icount_decr_ptr->u16.high, -1); + qatomic_fetch_or_release(&cpu->exit_request_pending, 1); } static void cpu_common_noop(CPUState *cpu) diff --git a/qemu/include/exec/cpu-all.h b/qemu/include/exec/cpu-all.h index d61c4acc59..b4fd076d0a 100644 --- a/qemu/include/exec/cpu-all.h +++ b/qemu/include/exec/cpu-all.h @@ -330,8 +330,8 @@ CPUArchState *cpu_copy(CPUArchState *env); #define TLB_MMIO (1 << (TARGET_PAGE_BITS_MIN - 3)) /* Set if TLB entry contains a watchpoint. */ #define TLB_WATCHPOINT (1 << (TARGET_PAGE_BITS_MIN - 4)) -/* Set if TLB entry requires byte swap. */ -#define TLB_BSWAP (1 << (TARGET_PAGE_BITS_MIN - 5)) +/* Set if the slow path must be used; more flags are in CPUTLBEntryFull. */ +#define TLB_FORCE_SLOW (1 << (TARGET_PAGE_BITS_MIN - 5)) /* Set if TLB entry writes ignored. */ #define TLB_DISCARD_WRITE (1 << (TARGET_PAGE_BITS_MIN - 6)) @@ -340,7 +340,14 @@ CPUArchState *cpu_copy(CPUArchState *env); */ #define TLB_FLAGS_MASK \ (TLB_INVALID_MASK | TLB_NOTDIRTY | TLB_MMIO \ - | TLB_WATCHPOINT | TLB_BSWAP | TLB_DISCARD_WRITE) + | TLB_WATCHPOINT | TLB_FORCE_SLOW | TLB_DISCARD_WRITE) + +/* Flags stored in CPUTLBEntryFull.slow_flags[]. */ +#define TLB_BSWAP (1 << 0) +#define TLB_UC_HOOK (1 << 1) +#define TLB_SLOW_FLAGS_MASK (TLB_BSWAP | TLB_UC_HOOK) + +QEMU_BUILD_BUG_ON(TLB_FLAGS_MASK & TLB_SLOW_FLAGS_MASK); /** * tlb_hit_page: return true if page aligned @addr is a hit against the diff --git a/qemu/include/exec/cpu-defs.h b/qemu/include/exec/cpu-defs.h index ce13d78a12..0c37819324 100644 --- a/qemu/include/exec/cpu-defs.h +++ b/qemu/include/exec/cpu-defs.h @@ -112,7 +112,6 @@ typedef struct CPUTLBEntry { target_ulong addr_read; target_ulong addr_write; target_ulong addr_code; - hwaddr paddr; /* Addend to virtual address to get host address. IO accesses use the corresponding iotlb value. */ uintptr_t addend; @@ -153,6 +152,12 @@ typedef struct CPUTLBEntryFull { /* Base-2 logarithm of the translated page size. */ uint8_t lg_page_size; + /* Additional flags used only after entering the slow path. */ + uint8_t slow_flags[MMU_ACCESS_COUNT]; + + /* Index of the translated MemoryRegionSection. */ + uint16_t section_index; + /* Target-specific page-table metadata. */ #ifdef TARGET_PAGE_ENTRY_EXTRA TARGET_PAGE_ENTRY_EXTRA @@ -177,6 +182,10 @@ typedef struct CPUTLBDesc { /* maximum number of entries observed in the window */ size_t window_max_entries; size_t n_used_entries; + size_t collision_misses; + size_t collision_min_size; + size_t collision_floor; + int64_t collision_floor_expires_ns; /* The next index to use in the tlb victim table. */ size_t vindex; /* The tlb victim table, in two parts. */ @@ -206,6 +215,8 @@ typedef struct CPUTLBCommon { * Protected by tlb_c.lock. */ uint16_t dirty; + uint64_t mutation_epoch; + uint64_t table_epoch; /* * Statistics. These are not lock protected, but are read and * written atomically. This allows the monitor to print a snapshot diff --git a/qemu/include/exec/cpu_ldst.h b/qemu/include/exec/cpu_ldst.h index 3f504bd2b7..a21ef82f8e 100644 --- a/qemu/include/exec/cpu_ldst.h +++ b/qemu/include/exec/cpu_ldst.h @@ -185,5 +185,5 @@ void *tlb_vaddr_to_host(CPUArchState *env, abi_ptr addr, * Returns true when posible to translate, otherwhise false */ bool tlb_vaddr_to_paddr(CPUArchState *env, abi_ptr addr, - MMUAccessType access_type, int mmu_idx, target_ulong *paddr); + MMUAccessType access_type, int mmu_idx, hwaddr *paddr); #endif /* CPU_LDST_H */ diff --git a/qemu/include/exec/exec-all.h b/qemu/include/exec/exec-all.h index b912d63edf..3aa7cf72c5 100644 --- a/qemu/include/exec/exec-all.h +++ b/qemu/include/exec/exec-all.h @@ -23,6 +23,7 @@ #include "hw/core/cpu.h" #include "exec/tb-context.h" #include "exec/cpu_ldst.h" +#include "qemu/atomic.h" #include "sysemu/cpus.h" /* allow to see translation results - the slowdown should be negligible, so we leave it */ @@ -54,6 +55,9 @@ void restore_state_to_opc(CPUArchState *env, TranslationBlock *tb, * restored and the function returns false. */ bool cpu_restore_state(CPUState *cpu, uintptr_t searched_pc, bool will_exit); +bool cpu_restore_icount(CPUState *cpu, uintptr_t searched_pc); +void QEMU_NORETURN cpu_tcg_exit_request(struct uc_struct *uc, + uintptr_t retaddr); void QEMU_NORETURN cpu_loop_exit_noexc(CPUState *cpu); void QEMU_NORETURN cpu_io_recompile(CPUState *cpu, uintptr_t retaddr); @@ -80,7 +84,7 @@ void QEMU_NORETURN cpu_loop_exit_atomic(CPUState *cpu, uintptr_t pc); */ static inline bool cpu_loop_exit_requested(CPUState *cpu) { - return (int32_t)cpu_neg(cpu)->icount_decr.u32 < 0; + return (int32_t)qatomic_read(&cpu_neg(cpu)->icount_decr.u32) < 0; } void cpu_reloading_memory_map(void); @@ -401,14 +405,15 @@ static inline uint32_t tb_cflags(const TranslationBlock *tb) } /* current cflags for hashing/comparison */ -static inline uint32_t curr_cflags(void) +static inline uint32_t curr_cflags(struct uc_struct *uc) { - return 0; + return uc_uses_tcg_count(uc) ? CF_USE_ICOUNT : 0; } /* TranslationBlock invalidate API */ void tb_invalidate_phys_addr(AddressSpace *as, hwaddr addr, MemTxAttrs attrs); void tb_flush(CPUState *cpu); +void tb_flush_jit(CPUState *cpu); void tb_phys_invalidate(TCGContext *tcg_ctx, TranslationBlock *tb, tb_page_addr_t page_addr); TranslationBlock *tb_htable_lookup(CPUState *cpu, target_ulong pc, target_ulong cs_base, uint32_t flags, diff --git a/qemu/include/exec/gen-icount.h b/qemu/include/exec/gen-icount.h index 8b484cbc20..3b791406cd 100644 --- a/qemu/include/exec/gen-icount.h +++ b/qemu/include/exec/gen-icount.h @@ -32,17 +32,41 @@ static inline void gen_io_end(TCGContext *tcg_ctx) static inline void gen_tb_start(TCGContext *tcg_ctx, TranslationBlock *tb) { - TCGv_ptr puc = tcg_const_ptr(tcg_ctx, tcg_ctx->uc); - TCGv_i32 tmp = tcg_const_i32(tcg_ctx, 0); - // Unicorn: - // We CANT'T use brcondi_i32 here or we will fail liveness analysis - // because it marks the end of BB - if (tcg_ctx->delay_slot_flag != NULL) { - tcg_gen_mov_i32(tcg_ctx, tmp, tcg_ctx->delay_slot_flag); + if (tb_cflags(tb) & CF_USE_ICOUNT) { + TCGv_ptr puc = tcg_const_ptr(tcg_ctx, tcg_ctx->uc); + TCGv_ptr ttb = tcg_const_ptr(tcg_ctx, tb); + TCGv_i32 tmp = tcg_const_i32(tcg_ctx, 0); + TCGv_i32 insns = tcg_temp_new_i32(tcg_ctx); + + if (tcg_ctx->delay_slot_flag != NULL) { + tcg_gen_mov_i32(tcg_ctx, tmp, tcg_ctx->delay_slot_flag); + } + tcg_gen_movi_i32(tcg_ctx, insns, 0xdeadbeef); + tcg_ctx->icount_start_insn = tcg_last_op(tcg_ctx); + gen_helper_check_counted_entry(tcg_ctx, puc, tmp, ttb, insns); + tcg_temp_free_i32(tcg_ctx, insns); + tcg_temp_free_i32(tcg_ctx, tmp); + tcg_temp_free_ptr(tcg_ctx, ttb); + tcg_temp_free_ptr(tcg_ctx, puc); + } else { + TCGv_i32 exit_req = tcg_temp_new_i32(tcg_ctx); + TCGLabel *no_exit = gen_new_label(tcg_ctx); + + tcg_gen_ld_i32(tcg_ctx, exit_req, tcg_ctx->cpu_env, + offsetof(ArchCPU, parent_obj.exit_request_pending) - + offsetof(ArchCPU, env)); + tcg_gen_brcondi_i32(tcg_ctx, TCG_COND_EQ, exit_req, 0, no_exit); + { + TCGv_ptr puc = tcg_const_ptr(tcg_ctx, tcg_ctx->uc); + TCGv_i32 tmp = tcg_const_i32(tcg_ctx, 0); + + gen_helper_exit_request(tcg_ctx, puc, tmp); + tcg_temp_free_i32(tcg_ctx, tmp); + tcg_temp_free_ptr(tcg_ctx, puc); + } + gen_set_label(tcg_ctx, no_exit); + tcg_temp_free_i32(tcg_ctx, exit_req); } - gen_helper_check_exit_request(tcg_ctx, puc, tmp); - tcg_temp_free_i32(tcg_ctx, tmp); - tcg_temp_free_ptr(tcg_ctx, puc); } static inline void gen_tb_end(TCGContext *tcg_ctx, TranslationBlock *tb, int num_insns) diff --git a/qemu/include/exec/memory.h b/qemu/include/exec/memory.h index eafc1a5de1..7fc2955970 100644 --- a/qemu/include/exec/memory.h +++ b/qemu/include/exec/memory.h @@ -26,30 +26,34 @@ #define RAM_ADDR_INVALID (~(ram_addr_t)0) #define MAX_PHYS_ADDR_SPACE_BITS 62 -#define MAX_PHYS_ADDR (((hwaddr)1 << MAX_PHYS_ADDR_SPACE_BITS) - 1) +#define MAX_PHYS_ADDR (((hwaddr)1 << MAX_PHYS_ADDR_SPACE_BITS) - 1) typedef struct MemoryRegionOps MemoryRegionOps; +typedef struct UcMapping UcMapping; typedef struct IOMMUTLBEntry IOMMUTLBEntry; -typedef uint64_t (*uc_cb_mmio_read_t)(struct uc_struct *uc, uint64_t addr, unsigned size, void *user_data); -typedef void (*uc_cb_mmio_write_t)(struct uc_struct *uc, uint64_t addr, unsigned size, uint64_t data, void *user_data); +typedef uint64_t (*uc_cb_mmio_read_t)(struct uc_struct *uc, uint64_t addr, + unsigned size, void *user_data); +typedef void (*uc_cb_mmio_write_t)(struct uc_struct *uc, uint64_t addr, + unsigned size, uint64_t data, + void *user_data); /* See address_space_translate: bit 0 is read, bit 1 is write. */ typedef enum { IOMMU_NONE = 0, - IOMMU_RO = 1, - IOMMU_WO = 2, - IOMMU_RW = 3, + IOMMU_RO = 1, + IOMMU_WO = 2, + IOMMU_RW = 3, } IOMMUAccessFlags; #define IOMMU_ACCESS_FLAG(r, w) (((r) ? IOMMU_RO : 0) | ((w) ? IOMMU_WO : 0)) struct IOMMUTLBEntry { - AddressSpace *target_as; - hwaddr iova; - hwaddr translated_addr; - hwaddr addr_mask; /* 0xfff = 4k translation */ + AddressSpace *target_as; + hwaddr iova; + hwaddr translated_addr; + hwaddr addr_mask; /* 0xfff = 4k translation */ IOMMUAccessFlags perm; }; @@ -83,10 +87,10 @@ struct IOMMUNotifier { typedef struct IOMMUNotifier IOMMUNotifier; /* RAM is pre-allocated and passed into qemu_ram_alloc_from_ptr */ -#define RAM_PREALLOC (1 << 0) +#define RAM_PREALLOC (1 << 0) /* RAM is mmap-ed with MAP_SHARED */ -#define RAM_SHARED (1 << 1) +#define RAM_SHARED (1 << 1) /* Only a portion of RAM (used_length) is actually used, and migrated. * This used_length size can change across reboots. @@ -106,9 +110,8 @@ typedef struct IOMMUNotifier IOMMUNotifier; #define RAM_PMEM (1 << 5) static inline void iommu_notifier_init(IOMMUNotifier *n, IOMMUNotify fn, - IOMMUNotifierFlag flags, - hwaddr start, hwaddr end, - int iommu_idx) + IOMMUNotifierFlag flags, hwaddr start, + hwaddr end, int iommu_idx) { n->notify = fn; n->notifier_flags = flags; @@ -123,28 +126,19 @@ static inline void iommu_notifier_init(IOMMUNotifier *n, IOMMUNotify fn, struct MemoryRegionOps { /* Read from the memory region. @addr is relative to @mr; @size is * in bytes. */ - uint64_t (*read)(struct uc_struct *uc, - void *opaque, - hwaddr addr, + uint64_t (*read)(struct uc_struct *uc, void *opaque, hwaddr addr, unsigned size); /* Write to the memory region. @addr is relative to @mr; @size is * in bytes. */ - void (*write)(struct uc_struct *uc, - void *opaque, - hwaddr addr, - uint64_t data, - unsigned size); + void (*write)(struct uc_struct *uc, void *opaque, hwaddr addr, + uint64_t data, unsigned size); MemTxResult (*read_with_attrs)(struct uc_struct *uc, void *opaque, - hwaddr addr, - uint64_t *data, - unsigned size, + hwaddr addr, uint64_t *data, unsigned size, MemTxAttrs attrs); MemTxResult (*write_with_attrs)(struct uc_struct *, void *opaque, - hwaddr addr, - uint64_t data, - unsigned size, + hwaddr addr, uint64_t data, unsigned size, MemTxAttrs attrs); enum device_endian endianness; @@ -158,15 +152,14 @@ struct MemoryRegionOps { /* If true, unaligned accesses are supported. Otherwise unaligned * accesses throw machine checks. */ - bool unaligned; + bool unaligned; /* * If present, and returns #false, the transaction is not accepted * by the device (and results in machine dependent behaviour such * as a machine check exception). */ bool (*accepts)(struct uc_struct *uc, void *opaque, hwaddr addr, - unsigned size, bool is_write, - MemTxAttrs attrs); + unsigned size, bool is_write, MemTxAttrs attrs); } valid; /* Internal implementation constraints: */ struct { @@ -185,9 +178,7 @@ struct MemoryRegionOps { } impl; }; -enum IOMMUMemoryRegionAttr { - IOMMU_ATTR_SPAPR_TCE_FD -}; +enum IOMMUMemoryRegionAttr { IOMMU_ATTR_SPAPR_TCE_FD }; /** * IOMMUMemoryRegionClass: @@ -323,6 +314,10 @@ struct MemoryRegion { struct uc_struct *uc; uint32_t perms; hwaddr end; + UcMapping *uc_mapping; + MemoryRegion *mapping_next; + hwaddr mapping_offset; + uint32_t context_refs; }; struct IOMMUMemoryRegion { @@ -339,7 +334,7 @@ struct IOMMUMemoryRegion { #define IOMMU_MEMORY_REGION_CLASS(klass) ((IOMMUMemoryRegionClass *)klass) #define IOMMU_MEMORY_REGION_GET_CLASS(obj) (&((IOMMUMemoryRegion *)obj)->cc) -#define IOMMU_NOTIFIER_FOREACH(n, mr) \ +#define IOMMU_NOTIFIER_FOREACH(n, mr) \ QLIST_FOREACH((n), &(mr)->iommu_notify, node) /** @@ -402,8 +397,8 @@ struct MemoryListener { * @region_nop: * * Called during an address space update transaction, - * for a section of the address space that is in the same place in the address - * space as in the last transaction. + * for a section of the address space that is in the same place in the + * address space as in the last transaction. * * @listener: The #MemoryListener. * @section: The #MemoryRegionSection. @@ -452,7 +447,6 @@ static inline FlatView *address_space_to_flatview(AddressSpace *as) return as->current_map; } - /** * MemoryRegionSection: describes a fragment of a #MemoryRegion * @@ -476,12 +470,10 @@ struct MemoryRegionSection { static inline bool MemoryRegionSection_eq(MemoryRegionSection *a, MemoryRegionSection *b) { - return a->mr == b->mr && - a->fv == b->fv && + return a->mr == b->mr && a->fv == b->fv && a->offset_within_region == b->offset_within_region && a->offset_within_address_space == b->offset_within_address_space && - int128_eq(a->size, b->size) && - a->readonly == b->readonly; + int128_eq(a->size, b->size) && a->readonly == b->readonly; } /** @@ -493,9 +485,7 @@ static inline bool MemoryRegionSection_eq(MemoryRegionSection *a, * @mr: the #MemoryRegion to be initialized * @size: size of the region; any subregions beyond this size will be clipped */ -void memory_region_init(struct uc_struct *uc, - MemoryRegion *mr, - uint64_t size); +void memory_region_init(struct uc_struct *uc, MemoryRegion *mr, uint64_t size); /** * memory_region_ref: Add 1 to a memory region's reference count @@ -526,10 +516,8 @@ void memory_region_ref(MemoryRegion *mr); * @opaque: passed to the read and write callbacks of the @ops structure. * @size: size of the region. */ -void memory_region_init_io(struct uc_struct *uc, - MemoryRegion *mr, - const MemoryRegionOps *ops, - void *opaque, +void memory_region_init_io(struct uc_struct *uc, MemoryRegion *mr, + const MemoryRegionOps *ops, void *opaque, uint64_t size); /** @@ -544,10 +532,8 @@ void memory_region_init_io(struct uc_struct *uc, * Note that this function does not do anything to cause the data in the * RAM memory region to be migrated; that is the responsibility of the caller. */ -void memory_region_init_ram_ptr(struct uc_struct *uc, - MemoryRegion *mr, - uint64_t size, - void *ptr); +void memory_region_init_ram_ptr(struct uc_struct *uc, MemoryRegion *mr, + uint64_t size, void *ptr); /** * memory_region_init_ram - Initialize RAM memory region. Accesses into the @@ -568,10 +554,8 @@ void memory_region_init_ram_ptr(struct uc_struct *uc, * We should lift this restriction and allow arbitrary Objects. * If you pass a non-NULL non-device @owner then we will assert. */ -void memory_region_init_ram(struct uc_struct *uc, - MemoryRegion *mr, - uint64_t size, - uint32_t perms); +void memory_region_init_ram(struct uc_struct *uc, MemoryRegion *mr, + uint64_t size, uint32_t perms); /** * memory_region_size: get a memory region's size. @@ -603,7 +587,7 @@ static inline bool memory_region_is_ram(MemoryRegion *mr) static inline IOMMUMemoryRegion *memory_region_get_iommu(MemoryRegion *mr) { if (mr->is_iommu) { - return (IOMMUMemoryRegion *) mr; + return (IOMMUMemoryRegion *)mr; } return NULL; } @@ -617,8 +601,8 @@ static inline IOMMUMemoryRegion *memory_region_get_iommu(MemoryRegion *mr) * * @iommu_mr: the memory region being queried */ -static inline IOMMUMemoryRegionClass *memory_region_get_iommu_class_nocheck( - IOMMUMemoryRegion *iommu_mr) +static inline IOMMUMemoryRegionClass * +memory_region_get_iommu_class_nocheck(IOMMUMemoryRegion *iommu_mr) { return &iommu_mr->cc; } @@ -640,7 +624,8 @@ static inline IOMMUMemoryRegionClass *memory_region_get_iommu_class_nocheck( * @ptr: the host pointer to be converted * @offset: the offset within memory region */ -MemoryRegion *memory_region_from_host(struct uc_struct *uc, void *ptr, ram_addr_t *offset); +MemoryRegion *memory_region_from_host(struct uc_struct *uc, void *ptr, + ram_addr_t *offset); /** * memory_region_set_readonly: Turn a memory region read-only (or read-write) @@ -683,8 +668,7 @@ void *memory_region_get_ram_ptr(MemoryRegion *mr); * @offset: the offset relative to @mr where @subregion is added. * @subregion: the subregion to be added. */ -void memory_region_add_subregion(MemoryRegion *mr, - hwaddr offset, +void memory_region_add_subregion(MemoryRegion *mr, hwaddr offset, MemoryRegion *subregion); /** @@ -704,10 +688,8 @@ void memory_region_add_subregion(MemoryRegion *mr, * @subregion: the subregion to be added. * @priority: used for resolving overlaps; highest priority wins. */ -void memory_region_add_subregion_overlap(MemoryRegion *mr, - hwaddr offset, - MemoryRegion *subregion, - int priority); +void memory_region_add_subregion_overlap(MemoryRegion *mr, hwaddr offset, + MemoryRegion *subregion, int priority); /** * memory_region_filter_subregions: filter subregios by priority. @@ -732,8 +714,7 @@ ram_addr_t memory_region_get_ram_addr(MemoryRegion *mr); * @mr: the container to be updated. * @subregion: the region being removed; must be a current subregion of @mr. */ -void memory_region_del_subregion(MemoryRegion *mr, - MemoryRegion *subregion); +void memory_region_del_subregion(MemoryRegion *mr, MemoryRegion *subregion); /** * memory_region_find: translate an address/size relative to a @@ -763,8 +744,8 @@ void memory_region_del_subregion(MemoryRegion *mr, * @addr: start of the area within @as to be searched * @size: size of the area to be searched */ -MemoryRegionSection memory_region_find(MemoryRegion *mr, - hwaddr addr, uint64_t size); +MemoryRegionSection memory_region_find(MemoryRegion *mr, hwaddr addr, + uint64_t size); /** * memory_listener_register: register callbacks to be called when memory @@ -794,9 +775,7 @@ void memory_listener_unregister(MemoryListener *listener); * @attrs: memory transaction attributes to use for the access */ MemTxResult memory_region_dispatch_read(struct uc_struct *uc, MemoryRegion *mr, - hwaddr addr, - uint64_t *pval, - MemOp op, + hwaddr addr, uint64_t *pval, MemOp op, MemTxAttrs attrs); /** * memory_region_dispatch_write: perform a write directly to the specified @@ -809,9 +788,7 @@ MemTxResult memory_region_dispatch_read(struct uc_struct *uc, MemoryRegion *mr, * @attrs: memory transaction attributes to use for the access */ MemTxResult memory_region_dispatch_write(struct uc_struct *uc, MemoryRegion *mr, - hwaddr addr, - uint64_t data, - MemOp op, + hwaddr addr, uint64_t data, MemOp op, MemTxAttrs attrs); /** @@ -820,16 +797,15 @@ MemTxResult memory_region_dispatch_write(struct uc_struct *uc, MemoryRegion *mr, * @as: an uninitialized #AddressSpace * @root: a #MemoryRegion that routes addresses for the address space */ -void address_space_init(struct uc_struct *uc, - AddressSpace *as, +void address_space_init(struct uc_struct *uc, AddressSpace *as, MemoryRegion *root); /** * address_space_destroy: destroy an address space * - * Releases all resources associated with an address space. After an address space - * is destroyed, its root memory region (given by address_space_init()) may be destroyed - * as well. + * Releases all resources associated with an address space. After an address + * space is destroyed, its root memory region (given by address_space_init()) + * may be destroyed as well. * * @as: address space to be destroyed */ @@ -859,9 +835,8 @@ void address_space_remove_listeners(AddressSpace *as); * @len: the number of bytes to read or write * @is_write: indicates the transfer direction */ -MemTxResult address_space_rw(AddressSpace *as, hwaddr addr, - MemTxAttrs attrs, void *buf, - hwaddr len, bool is_write); +MemTxResult address_space_rw(AddressSpace *as, hwaddr addr, MemTxAttrs attrs, + void *buf, hwaddr len, bool is_write); /** * address_space_write: write to address space. @@ -876,8 +851,7 @@ MemTxResult address_space_rw(AddressSpace *as, hwaddr addr, * @buf: buffer with the data transferred * @len: the number of bytes to write */ -MemTxResult address_space_write(AddressSpace *as, hwaddr addr, - MemTxAttrs attrs, +MemTxResult address_space_write(AddressSpace *as, hwaddr addr, MemTxAttrs attrs, const void *buf, hwaddr len); /** @@ -903,8 +877,8 @@ MemTxResult address_space_write(AddressSpace *as, hwaddr addr, * @len: the number of bytes to write */ MemTxResult address_space_write_rom(AddressSpace *as, hwaddr addr, - MemTxAttrs attrs, - const void *buf, hwaddr len); + MemTxAttrs attrs, const void *buf, + hwaddr len); /* address_space_ld*: load from an address space * address_space_st*: store to an address space @@ -928,21 +902,21 @@ MemTxResult address_space_write_rom(AddressSpace *as, hwaddr addr, */ #ifdef UNICORN_ARCH_POSTFIX -#define SUFFIX UNICORN_ARCH_POSTFIX +#define SUFFIX UNICORN_ARCH_POSTFIX #else #define SUFFIX #endif -#define ARG1 as -#define ARG1_DECL AddressSpace *as +#define ARG1 as +#define ARG1_DECL AddressSpace *as #include "exec/memory_ldst.inc.h" #ifdef UNICORN_ARCH_POSTFIX -#define SUFFIX UNICORN_ARCH_POSTFIX +#define SUFFIX UNICORN_ARCH_POSTFIX #else #define SUFFIX #endif -#define ARG1 as -#define ARG1_DECL AddressSpace *as +#define ARG1 as +#define ARG1_DECL AddressSpace *as #include "exec/memory_ldst_phys.inc.h" struct MemoryRegionCache { @@ -954,8 +928,7 @@ struct MemoryRegionCache { bool is_write; }; -#define MEMORY_REGION_CACHE_INVALID ((MemoryRegionCache) { .mrs.mr = NULL }) - +#define MEMORY_REGION_CACHE_INVALID ((MemoryRegionCache){.mrs.mr = NULL}) /* address_space_ld*_cached: load from a cached #MemoryRegion * address_space_st*_cached: store into a cached #MemoryRegion @@ -982,28 +955,33 @@ struct MemoryRegionCache { */ #ifdef UNICORN_ARCH_POSTFIX -#define SUFFIX glue(_cached_slow, UNICORN_ARCH_POSTFIX) +#define SUFFIX glue(_cached_slow, UNICORN_ARCH_POSTFIX) #else -#define SUFFIX _cached_slow +#define SUFFIX _cached_slow #endif -#define ARG1 cache -#define ARG1_DECL MemoryRegionCache *cache +#define ARG1 cache +#define ARG1_DECL MemoryRegionCache *cache #include "exec/memory_ldst.inc.h" /* Inline fast path for direct RAM access. */ #ifdef UNICORN_ARCH_POSTFIX -static inline uint8_t glue(address_space_ldub_cached, UNICORN_ARCH_POSTFIX)(struct uc_struct *uc, MemoryRegionCache *cache, +static inline uint8_t glue(address_space_ldub_cached, + UNICORN_ARCH_POSTFIX)(struct uc_struct *uc, + MemoryRegionCache *cache, #else -static inline uint8_t address_space_ldub_cached(struct uc_struct *uc, MemoryRegionCache *cache, +static inline uint8_t address_space_ldub_cached(struct uc_struct *uc, + MemoryRegionCache *cache, #endif - hwaddr addr, MemTxAttrs attrs, MemTxResult *result) + hwaddr addr, MemTxAttrs attrs, + MemTxResult *result) { assert(addr < cache->len); if (likely(cache->ptr)) { return ldub_p((char *)cache->ptr + addr); } else { #ifdef UNICORN_ARCH_POSTFIX - return glue(address_space_ldub_cached_slow, UNICORN_ARCH_POSTFIX)(uc, cache, addr, attrs, result); + return glue(address_space_ldub_cached_slow, + UNICORN_ARCH_POSTFIX)(uc, cache, addr, attrs, result); #else return address_space_ldub_cached_slow(uc, cache, addr, attrs, result); #endif @@ -1011,9 +989,11 @@ static inline uint8_t address_space_ldub_cached(struct uc_struct *uc, MemoryRegi } #ifdef UNICORN_ARCH_POSTFIX -static inline void glue(address_space_stb_cached, UNICORN_ARCH_POSTFIX)(struct uc_struct *uc, MemoryRegionCache *cache, +static inline void glue(address_space_stb_cached, UNICORN_ARCH_POSTFIX)( + struct uc_struct *uc, MemoryRegionCache *cache, #else -static inline void address_space_stb_cached(struct uc_struct *uc, MemoryRegionCache *cache, +static inline void address_space_stb_cached( + struct uc_struct *uc, MemoryRegionCache *cache, #endif hwaddr addr, uint32_t val, MemTxAttrs attrs, MemTxResult *result) { @@ -1022,26 +1002,27 @@ static inline void address_space_stb_cached(struct uc_struct *uc, MemoryRegionCa stb_p((char *)cache->ptr + addr, val); } else { #ifdef UNICORN_ARCH_POSTFIX - glue(address_space_stb_cached_slow, UNICORN_ARCH_POSTFIX)(uc, cache, addr, val, attrs, result); + glue(address_space_stb_cached_slow, + UNICORN_ARCH_POSTFIX)(uc, cache, addr, val, attrs, result); #else address_space_stb_cached_slow(uc, cache, addr, val, attrs, result); #endif } } -#define ENDIANNESS _le +#define ENDIANNESS _le #include "exec/memory_ldst_cached.inc.h" -#define ENDIANNESS _be +#define ENDIANNESS _be #include "exec/memory_ldst_cached.inc.h" #ifdef UNICORN_ARCH_POSTFIX -#define SUFFIX glue(_cached, UNICORN_ARCH_POSTFIX) +#define SUFFIX glue(_cached, UNICORN_ARCH_POSTFIX) #else -#define SUFFIX _cached +#define SUFFIX _cached #endif -#define ARG1 cache -#define ARG1_DECL MemoryRegionCache *cache +#define ARG1 cache +#define ARG1_DECL MemoryRegionCache *cache #include "exec/memory_ldst_phys.inc.h" /* address_space_translate: translate an address range into an address space @@ -1058,17 +1039,16 @@ static inline void address_space_stb_cached(struct uc_struct *uc, MemoryRegionCa * @attrs: memory attributes */ MemoryRegion *flatview_translate(struct uc_struct *uc, FlatView *fv, - hwaddr addr, hwaddr *xlat, - hwaddr *len, bool is_write, - MemTxAttrs attrs); + hwaddr addr, hwaddr *xlat, hwaddr *len, + bool is_write, MemTxAttrs attrs); static inline MemoryRegion *address_space_translate(AddressSpace *as, hwaddr addr, hwaddr *xlat, hwaddr *len, bool is_write, MemTxAttrs attrs) { - return flatview_translate(as->uc, address_space_to_flatview(as), - addr, xlat, len, is_write, attrs); + return flatview_translate(as->uc, address_space_to_flatview(as), addr, xlat, + len, is_write, attrs); } /* address_space_access_valid: check for validity of accessing an address @@ -1104,10 +1084,11 @@ bool address_space_access_valid(AddressSpace *as, hwaddr addr, hwaddr len, * @is_write: indicates the transfer direction * @attrs: memory attributes */ -void *address_space_map(AddressSpace *as, hwaddr addr, - hwaddr *plen, bool is_write, MemTxAttrs attrs); +void *address_space_map(AddressSpace *as, hwaddr addr, hwaddr *plen, + bool is_write, MemTxAttrs attrs); -/* address_space_unmap: Unmaps a memory region previously mapped by address_space_map() +/* address_space_unmap: Unmaps a memory region previously mapped by + * address_space_map() * * Will also mark the memory as dirty if @is_write == %true. @access_len gives * the amount of memory that was actually read or written by the caller. @@ -1121,15 +1102,15 @@ void *address_space_map(AddressSpace *as, hwaddr addr, void address_space_unmap(AddressSpace *as, void *buffer, hwaddr len, bool is_write, hwaddr access_len); - /* Internal functions, part of the implementation of address_space_read. */ MemTxResult address_space_read_full(AddressSpace *as, hwaddr addr, MemTxAttrs attrs, void *buf, hwaddr len); -MemTxResult flatview_read_continue(struct uc_struct *, FlatView *fv, hwaddr addr, - MemTxAttrs attrs, void *buf, +MemTxResult flatview_read_continue(struct uc_struct *, FlatView *fv, + hwaddr addr, MemTxAttrs attrs, void *buf, hwaddr len, hwaddr addr1, hwaddr l, MemoryRegion *mr); -void *qemu_map_ram_ptr(struct uc_struct *uc, RAMBlock *ram_block, ram_addr_t addr); +void *qemu_map_ram_ptr(struct uc_struct *uc, RAMBlock *ram_block, + ram_addr_t addr); static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write) { @@ -1158,9 +1139,9 @@ static inline __attribute__((__always_inline__)) #else static inline #endif -MemTxResult address_space_read(AddressSpace *as, hwaddr addr, - MemTxAttrs attrs, void *buf, - hwaddr len) +MemTxResult +address_space_read(AddressSpace *as, hwaddr addr, MemTxAttrs attrs, void *buf, + hwaddr len) { MemTxResult result = MEMTX_OK; #ifndef _MSC_VER @@ -1178,8 +1159,8 @@ MemTxResult address_space_read(AddressSpace *as, hwaddr addr, ptr = qemu_map_ram_ptr(mr->uc, mr->ram_block, addr1); memcpy(buf, ptr, len); } else { - result = flatview_read_continue(as->uc, fv, addr, attrs, buf, len, - addr1, l, mr); + result = flatview_read_continue(as->uc, fv, addr, attrs, buf, + len, addr1, l, mr); } } } else { @@ -1211,15 +1192,32 @@ static inline MemOp devend_memop(enum device_endian end) } #endif -MemoryRegion *memory_map(struct uc_struct *uc, hwaddr begin, size_t size, uint32_t perms); -MemoryRegion *memory_map_ptr(struct uc_struct *uc, hwaddr begin, size_t size, uint32_t perms, void *ptr); - MemoryRegion *memory_map_io(struct uc_struct *uc, ram_addr_t begin, size_t size, uc_cb_mmio_read_t read_cb, - uc_cb_mmio_write_t write_cb, void *user_data_read, void *user_data_write); -MemoryRegion *memory_cow(struct uc_struct *uc, MemoryRegion *parrent, hwaddr begin, size_t size); -void memory_unmap(struct uc_struct *uc, MemoryRegion *mr); -void memory_moveout(struct uc_struct *uc, MemoryRegion *mr); -void memory_movein(struct uc_struct *uc, MemoryRegion *mr); +MemoryRegion *memory_map(struct uc_struct *uc, hwaddr begin, size_t size, + uint32_t perms); +MemoryRegion *memory_map_ptr(struct uc_struct *uc, hwaddr begin, size_t size, + uint32_t perms, void *ptr); +MemoryRegion *memory_map_io(struct uc_struct *uc, ram_addr_t begin, size_t size, + uc_cb_mmio_read_t read_cb, + uc_cb_mmio_write_t write_cb, void *user_data_read, + void *user_data_write); +MemoryRegion *memory_cow(struct uc_struct *uc, UcMapping *mapping, + MemoryRegion *current, hwaddr begin, size_t size); +void memory_unmap(struct uc_struct *uc, UcMapping *mapping); +void memory_moveout(struct uc_struct *uc, UcMapping *mapping, + bool update_topology); +void memory_movein(struct uc_struct *uc, UcMapping *mapping, + bool update_topology); +void memory_mapping_restore_topology(struct uc_struct *uc, UcMapping *mapping, + MemoryRegion *const *regions, + uint32_t region_count, + bool update_topology); +void memory_mapping_free(UcMapping *mapping); +void memory_mapping_prune(UcMapping *mapping); +void memory_mapping_normalize(UcMapping *mapping); int memory_free(struct uc_struct *uc); -bool flatview_copy(struct uc_struct *uc, FlatView *dst, FlatView *src, bool update_dispatcher); +bool flatview_copy(struct uc_struct *uc, FlatView *dst, FlatView *src, + bool update_dispatcher); +bool flatview_reserve(FlatView *view, unsigned int count); +void address_space_restore_flatview(AddressSpace *as, FlatView *view); #endif diff --git a/qemu/include/hw/core/cpu.h b/qemu/include/hw/core/cpu.h index e37f104b75..f7eaae761b 100644 --- a/qemu/include/hw/core/cpu.h +++ b/qemu/include/hw/core/cpu.h @@ -42,7 +42,8 @@ typedef uint64_t vaddr; typedef enum MMUAccessType { MMU_DATA_LOAD = 0, MMU_DATA_STORE = 1, - MMU_INST_FETCH = 2 + MMU_INST_FETCH = 2, + MMU_ACCESS_COUNT } MMUAccessType; typedef struct CPUWatchpoint CPUWatchpoint; @@ -301,7 +302,6 @@ struct CPUState { bool stopped; bool unplug; bool crash_occurred; - bool exit_request; bool in_exclusive_context; uint32_t cflags_next_tb; /* updates protected by BQL */ @@ -356,9 +356,11 @@ struct CPUState { /* pointer to CPUArchState.cc */ struct CPUClass *cc; - // Set to force TCG to stop executing linked TBs for this - // CPU and return to its top level loop. - volatile sig_atomic_t tcg_exit_req; + /* + * Set after exit payload state to force TCG back to the CPU loop. + * Generated code relies on this being a naturally aligned atomic word. + */ + QEMU_ALIGN(4, uint32_t exit_request_pending); }; #define CPU(obj) ((CPUState *)(obj)) diff --git a/qemu/include/qemu/atomic.h b/qemu/include/qemu/atomic.h index 9844160a12..151325846c 100644 --- a/qemu/include/qemu/atomic.h +++ b/qemu/include/qemu/atomic.h @@ -139,6 +139,11 @@ atomic_xchg__nocheck(ptr, i); \ }) +#define atomic_xchg_acquire(ptr, i) ({ \ + QEMU_BUILD_BUG_ON(sizeof(*ptr) > ATOMIC_REG_SIZE); \ + __atomic_exchange_n(ptr, (i), __ATOMIC_ACQUIRE); \ +}) + /* Returns the eventual value, failed or not */ #define atomic_cmpxchg__nocheck(ptr, old, new) ({ \ typeof_strip_qual(*ptr) _old = (old); \ @@ -160,6 +165,8 @@ #define atomic_fetch_and(ptr, n) __atomic_fetch_and(ptr, n, __ATOMIC_SEQ_CST) #define atomic_fetch_or(ptr, n) __atomic_fetch_or(ptr, n, __ATOMIC_SEQ_CST) #define atomic_fetch_xor(ptr, n) __atomic_fetch_xor(ptr, n, __ATOMIC_SEQ_CST) +#define atomic_fetch_or_release(ptr, n) \ + __atomic_fetch_or(ptr, n, __ATOMIC_RELEASE) #define atomic_inc_fetch(ptr) __atomic_add_fetch(ptr, 1, __ATOMIC_SEQ_CST) #define atomic_dec_fetch(ptr) __atomic_sub_fetch(ptr, 1, __ATOMIC_SEQ_CST) @@ -246,6 +253,9 @@ /* Provide shorter names for GCC atomic builtins. */ #ifdef _MSC_VER // these return the new value (so we make it return the previous value) +#define atomic_xchg_acquire(ptr, i) \ + InterlockedExchange((volatile long *)(ptr), (long)(i)) + #define atomic_fetch_inc(ptr) ((InterlockedIncrement(ptr))-1) #define atomic_fetch_dec(ptr) ((InterlockedDecrement(ptr))+1) #define atomic_fetch_add(ptr, n) ((InterlockedAdd(ptr, n))-n) @@ -253,6 +263,8 @@ #define atomic_fetch_and(ptr, n) ((InterlockedAnd(ptr, n))) #define atomic_fetch_or(ptr, n) ((InterlockedOr(ptr, n))) #define atomic_fetch_xor(ptr, n) ((InterlockedXor(ptr, n))) +#define atomic_fetch_or_release(ptr, n) \ + InterlockedOr((volatile long *)(ptr), (long)(n)) #define atomic_inc_fetch(ptr) (InterlockedIncrement((long*)(ptr))) #define atomic_dec_fetch(ptr) (InterlockedDecrement((long*)(ptr))) @@ -274,6 +286,8 @@ #define atomic_xor(ptr, n) ((void) InterlockedXor(ptr, n)) #else // GCC/clang // these return the previous value +#define atomic_xchg_acquire(ptr, i) atomic_xchg(ptr, i) + #define atomic_fetch_inc(ptr) __sync_fetch_and_add(ptr, 1) #define atomic_fetch_dec(ptr) __sync_fetch_and_add(ptr, -1) #define atomic_fetch_add(ptr, n) __sync_fetch_and_add(ptr, n) @@ -281,6 +295,7 @@ #define atomic_fetch_and(ptr, n) __sync_fetch_and_and(ptr, n) #define atomic_fetch_or(ptr, n) __sync_fetch_and_or(ptr, n) #define atomic_fetch_xor(ptr, n) __sync_fetch_and_xor(ptr, n) +#define atomic_fetch_or_release(ptr, n) __sync_fetch_and_or(ptr, n) #define atomic_inc_fetch(ptr) __sync_add_and_fetch(ptr, 1) #define atomic_dec_fetch(ptr) __sync_add_and_fetch(ptr, -1) @@ -322,6 +337,9 @@ #ifndef qatomic_rcu_set #define qatomic_rcu_set atomic_rcu_set #endif +#ifndef qatomic_xchg_acquire +#define qatomic_xchg_acquire atomic_xchg_acquire +#endif #ifndef qatomic_xchg__nocheck #define qatomic_xchg__nocheck atomic_xchg__nocheck #endif @@ -355,6 +373,9 @@ #ifndef qatomic_fetch_xor #define qatomic_fetch_xor atomic_fetch_xor #endif +#ifndef qatomic_fetch_or_release +#define qatomic_fetch_or_release atomic_fetch_or_release +#endif #ifndef qatomic_inc_fetch #define qatomic_inc_fetch atomic_inc_fetch #endif diff --git a/qemu/include/tcg/tcg-op.h b/qemu/include/tcg/tcg-op.h index f3e10ac069..9729c67601 100644 --- a/qemu/include/tcg/tcg-op.h +++ b/qemu/include/tcg/tcg-op.h @@ -29,6 +29,10 @@ #include "exec/helper-proto.h" #include "exec/helper-gen.h" +#define UC_TRACE_SIZE_UNKNOWN ((int32_t)0xf1f1f1f1U) + +static inline void gen_active_tb(TCGContext *tcg_ctx); + static inline void gen_uc_tracecode(TCGContext *tcg_ctx, int32_t size, int32_t type, void *uc, uint64_t pc) { TCGv_i32 tsize = tcg_const_i32(tcg_ctx, size); @@ -38,6 +42,7 @@ static inline void gen_uc_tracecode(TCGContext *tcg_ctx, int32_t size, int32_t t TCGv_ptr tdata; uc_engine* puc = uc; struct list_item *cur; + struct list_item *first_match = NULL; struct hook* hk; TCGTemp* args[] = { tcgv_ptr_temp(tcg_ctx, tuc), @@ -46,8 +51,30 @@ static inline void gen_uc_tracecode(TCGContext *tcg_ctx, int32_t size, int32_t t 0 }; + gen_active_tb(tcg_ctx); + const int hook_type = type & UC_HOOK_IDX_MASK; - if (puc->hooks_count[hook_type] == 1 && !(type & UC_HOOK_FLAG_NO_STOP)) { + if (hook_type == UC_HOOK_CODE_IDX && + puc->hooks_count[hook_type] > 1 && puc->count_hook == 0 && + !(type & UC_HOOK_FLAG_NO_STOP) && + tcg_ctx->delay_slot_flag == NULL) { + TCGv_ptr titem; + + for (cur = puc->hook[hook_type].head; cur != NULL; cur = cur->next) { + hk = cur->data; + if (HOOK_BOUND_CHECK(hk, pc)) { + first_match = cur; + break; + } + } + if (first_match != NULL) { + titem = tcg_const_ptr(tcg_ctx, first_match); + gen_helper_uc_tracecode_single(tcg_ctx, titem, tsize, tuc, tpc); + tcg_temp_free_ptr(tcg_ctx, titem); + tcg_ctx->skip_next_exit_check = true; + } + } else if (puc->hooks_count[hook_type] == 1 && + !(type & UC_HOOK_FLAG_NO_STOP)) { cur = puc->hook[hook_type].head; while (cur) { @@ -61,6 +88,21 @@ static inline void gen_uc_tracecode(TCGContext *tcg_ctx, int32_t size, int32_t t cur = cur->next; } + } else if (!(type & UC_HOOK_FLAG_NO_STOP) && puc->count_hook == 0) { + TCGv_ptr titem; + + for (cur = puc->hook[hook_type].head; cur != NULL; cur = cur->next) { + hk = cur->data; + if (HOOK_BOUND_CHECK(hk, pc)) { + first_match = cur; + break; + } + } + if (first_match != NULL) { + titem = tcg_const_ptr(tcg_ctx, first_match); + gen_helper_uc_tracecode_single(tcg_ctx, titem, tsize, tuc, tpc); + tcg_temp_free_ptr(tcg_ctx, titem); + } } else { ttype = tcg_const_i32(tcg_ctx, type); gen_helper_uc_tracecode(tcg_ctx, tsize, ttype, tuc, tpc); @@ -84,6 +126,7 @@ static inline void gen_uc_traceopcode(TCGContext *tcg_ctx, void* hook, TCGv_i64 // TCGv_i64 targ1 = arg1; // TCGv_i64 targ2 = arg2; // #endif + gen_active_tb(tcg_ctx); gen_helper_uc_traceopcode(tcg_ctx, thook, arg1, arg2, tsz, tuc, tpc); tcg_temp_free_i32(tcg_ctx, tsz); tcg_temp_free_i64(tcg_ctx, tpc); @@ -378,6 +421,7 @@ void tcg_gen_setcond_i32(TCGContext *tcg_ctx, TCGCond cond, TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2); void tcg_gen_setcondi_i32(TCGContext *tcg_ctx, TCGCond cond, TCGv_i32 ret, TCGv_i32 arg1, int32_t arg2); + void tcg_gen_movcond_i32(TCGContext *tcg_ctx, TCGCond cond, TCGv_i32 ret, TCGv_i32 c1, TCGv_i32 c2, TCGv_i32 v1, TCGv_i32 v2); void tcg_gen_add2_i32(TCGContext *tcg_ctx, TCGv_i32 rl, TCGv_i32 rh, TCGv_i32 al, @@ -399,6 +443,31 @@ void tcg_gen_umin_i32(TCGContext *tcg_ctx, TCGv_i32, TCGv_i32 arg1, TCGv_i32 arg void tcg_gen_umax_i32(TCGContext *tcg_ctx, TCGv_i32, TCGv_i32 arg1, TCGv_i32 arg2); void tcg_gen_abs_i32(TCGContext *tcg_ctx, TCGv_i32, TCGv_i32); +static inline void gen_uc_tracefetch(TCGContext *tcg_ctx, int32_t size, + int32_t type, void *uc, uint64_t pc) +{ + TCGLabel *skip = NULL; + TCGv_i32 tsize = tcg_const_i32(tcg_ctx, size); + TCGv_i32 ttype = tcg_const_i32(tcg_ctx, type); + TCGv_ptr tuc = tcg_const_ptr(tcg_ctx, uc); + TCGv_i64 tpc = tcg_const_i64(tcg_ctx, pc); + + if (size == UC_TRACE_SIZE_UNKNOWN) { + skip = gen_new_label(tcg_ctx); + tcg_gen_brcondi_i32(tcg_ctx, TCG_COND_EQ, tsize, + UC_TRACE_SIZE_UNKNOWN, skip); + } + gen_active_tb(tcg_ctx); + gen_helper_uc_tracecode(tcg_ctx, tsize, ttype, tuc, tpc); + if (skip) { + gen_set_label(tcg_ctx, skip); + } + tcg_temp_free_i64(tcg_ctx, tpc); + tcg_temp_free_ptr(tcg_ctx, tuc); + tcg_temp_free_i32(tcg_ctx, ttype); + tcg_temp_free_i32(tcg_ctx, tsize); +} + static inline void tcg_gen_discard_i32(TCGContext *tcg_ctx, TCGv_i32 arg) { tcg_gen_op1_i32(tcg_ctx, INDEX_op_discard, arg); @@ -1344,6 +1413,21 @@ static inline void tcg_gen_st_ptr(TCGContext *tcg_ctx, TCGv_ptr r, TCGv_ptr a, i glue(tcg_gen_st_, PTR)(tcg_ctx, (NAT)r, a, o); } +static inline void gen_active_tb(TCGContext *tcg_ctx) +{ + TCGv_ptr tuc = tcg_const_ptr(tcg_ctx, tcg_ctx->uc); + TCGv_ptr tframe = tcg_temp_new_ptr(tcg_ctx); + TCGv_ptr ttb = tcg_const_ptr(tcg_ctx, tcg_ctx->gen_tb); + + tcg_debug_assert(tcg_ctx->gen_tb != NULL); + tcg_gen_ld_ptr(tcg_ctx, tframe, tuc, + offsetof(struct uc_struct, active_tb_exec_frame)); + tcg_gen_st_ptr(tcg_ctx, ttb, tframe, offsetof(UcTbExecFrame, tb)); + tcg_temp_free_ptr(tcg_ctx, ttb); + tcg_temp_free_ptr(tcg_ctx, tframe); + tcg_temp_free_ptr(tcg_ctx, tuc); +} + static inline void tcg_gen_discard_ptr(TCGContext *tcg_ctx, TCGv_ptr a) { glue(tcg_gen_discard_,PTR)(tcg_ctx, (NAT)a); diff --git a/qemu/include/tcg/tcg.h b/qemu/include/tcg/tcg.h index 888117a59d..6208e21c12 100644 --- a/qemu/include/tcg/tcg.h +++ b/qemu/include/tcg/tcg.h @@ -592,6 +592,16 @@ typedef struct TCGProfile { int64_t table_op_count[NB_OPS]; } TCGProfile; +#define TCG_RESTORE_STATE_CACHE_BITS 8 +#define TCG_RESTORE_STATE_CACHE_SIZE (1 << TCG_RESTORE_STATE_CACHE_BITS) + +typedef struct TCGRestoreStateCacheEntry { + uintptr_t host_pc; + TranslationBlock *tb; + int insns_left; + target_ulong data[TARGET_INSN_START_WORDS]; +} TCGRestoreStateCacheEntry; + /* * We divide code_gen_buffer into equally-sized "regions" that TCG threads * dynamically allocate from as demand dictates. Given appropriate region @@ -659,9 +669,12 @@ struct TCGContext { void *code_gen_highwater; size_t tb_phys_invalidate_count; + TCGRestoreStateCacheEntry + restore_state_cache[TCG_RESTORE_STATE_CACHE_SIZE]; /* Track which vCPU triggers events */ CPUState *cpu; /* *_trans */ + TranslationBlock *gen_tb; /* These structures are private to tcg-target.inc.c. */ #ifdef TCG_TARGET_NEED_LDST_LABELS @@ -690,6 +703,7 @@ struct TCGContext { TBContext tb_ctx; /* qemu/include/exec/gen-icount.h */ TCGOp *icount_start_insn; + bool skip_next_exit_check; /* qemu/tcg/tcg.c */ GHashTable *helper_table; GHashTable *custom_helper_infos; // To support inline hooks. @@ -821,6 +835,28 @@ struct TCGContext { bool use_lsx_instructions; }; +static inline void tcg_restore_state_cache_clear(TCGContext *tcg_ctx) +{ + memset(tcg_ctx->restore_state_cache, 0, + sizeof(tcg_ctx->restore_state_cache)); +} + +static inline void tcg_restore_state_cache_remove(TCGContext *tcg_ctx, + TranslationBlock *tb) +{ + size_t i; + + for (i = 0; i < TCG_RESTORE_STATE_CACHE_SIZE; i++) { + TCGRestoreStateCacheEntry *entry = + &tcg_ctx->restore_state_cache[i]; + + if (entry->tb == tb) { + entry->host_pc = 0; + entry->tb = NULL; + } + } +} + static inline const void *tcg_splitwx_to_rx(void *rw) { return rw; @@ -1285,6 +1321,8 @@ static inline size_t tcg_current_code_size(TCGContext *s) /* Combine the MemOp and mmu_idx parameters into a single value. */ typedef uint32_t TCGMemOpIdx; +#define TCG_MO_EXIT_REQUEST (UINT32_C(1) << 31) + /** * make_memop_idx * @op: memory operation @@ -1306,7 +1344,12 @@ static inline TCGMemOpIdx make_memop_idx(MemOp op, unsigned idx) */ static inline MemOp get_memop(TCGMemOpIdx oi) { - return oi >> 4; + return (oi & ~TCG_MO_EXIT_REQUEST) >> 4; +} + +static inline bool memop_idx_has_exit_request(TCGMemOpIdx oi) +{ + return (oi & TCG_MO_EXIT_REQUEST) != 0; } /** diff --git a/qemu/m68k.h b/qemu/m68k.h index 69146b2752..48e5a20c62 100644 --- a/qemu/m68k.h +++ b/qemu/m68k.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _m68k #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_m68k #define gen_helper_check_exit_request gen_helper_check_exit_request_m68k +#define gen_helper_exit_request gen_helper_exit_request_m68k +#define helper_check_counted_entry helper_check_counted_entry_m68k +#define helper_check_exit_request helper_check_exit_request_m68k +#define helper_exit_request helper_exit_request_m68k +#define helper_uc_tracecode_single helper_uc_tracecode_single_m68k +#define cpu_restore_icount cpu_restore_icount_m68k +#define cpu_tcg_exit_request cpu_tcg_exit_request_m68k #define unicorn_fill_tlb unicorn_fill_tlb_m68k #define reg_read reg_read_m68k #define reg_write reg_write_m68k @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_m68k #define flatview_translate flatview_translate_m68k #define flatview_copy flatview_copy_m68k +#define flatview_reserve flatview_reserve_m68k +#define address_space_restore_flatview address_space_restore_flatview_m68k #define address_space_translate_for_iotlb address_space_translate_for_iotlb_m68k #define qemu_get_cpu qemu_get_cpu_m68k #define cpu_address_space_init cpu_address_space_init_m68k @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_m68k #define memory_moveout memory_moveout_m68k #define memory_movein memory_movein_m68k +#define memory_mapping_restore_topology memory_mapping_restore_topology_m68k +#define memory_mapping_free memory_mapping_free_m68k +#define memory_mapping_prune memory_mapping_prune_m68k +#define memory_mapping_normalize memory_mapping_normalize_m68k #define memory_free memory_free_m68k #define flatview_unref flatview_unref_m68k #define address_space_get_flatview address_space_get_flatview_m68k @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_m68k #define tb_cleanup tb_cleanup_m68k #define tb_flush tb_flush_m68k +#define tb_flush_jit tb_flush_jit_m68k #define tb_phys_invalidate tb_phys_invalidate_m68k #define tb_gen_code tb_gen_code_m68k #define tb_reset_jump tb_reset_jump_m68k diff --git a/qemu/mips.h b/qemu/mips.h index 2e2bc49a98..2730f01c97 100644 --- a/qemu/mips.h +++ b/qemu/mips.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _mips #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_mips #define gen_helper_check_exit_request gen_helper_check_exit_request_mips +#define gen_helper_exit_request gen_helper_exit_request_mips +#define helper_check_counted_entry helper_check_counted_entry_mips +#define helper_check_exit_request helper_check_exit_request_mips +#define helper_exit_request helper_exit_request_mips +#define helper_uc_tracecode_single helper_uc_tracecode_single_mips +#define cpu_restore_icount cpu_restore_icount_mips +#define cpu_tcg_exit_request cpu_tcg_exit_request_mips #define unicorn_fill_tlb unicorn_fill_tlb_mips #define reg_read reg_read_mips #define reg_write reg_write_mips @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_mips #define flatview_translate flatview_translate_mips #define flatview_copy flatview_copy_mips +#define flatview_reserve flatview_reserve_mips +#define address_space_restore_flatview address_space_restore_flatview_mips #define address_space_translate_for_iotlb address_space_translate_for_iotlb_mips #define qemu_get_cpu qemu_get_cpu_mips #define cpu_address_space_init cpu_address_space_init_mips @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_mips #define memory_moveout memory_moveout_mips #define memory_movein memory_movein_mips +#define memory_mapping_restore_topology memory_mapping_restore_topology_mips +#define memory_mapping_free memory_mapping_free_mips +#define memory_mapping_prune memory_mapping_prune_mips +#define memory_mapping_normalize memory_mapping_normalize_mips #define memory_free memory_free_mips #define flatview_unref flatview_unref_mips #define address_space_get_flatview address_space_get_flatview_mips @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_mips #define tb_cleanup tb_cleanup_mips #define tb_flush tb_flush_mips +#define tb_flush_jit tb_flush_jit_mips #define tb_phys_invalidate tb_phys_invalidate_mips #define tb_gen_code tb_gen_code_mips #define tb_reset_jump tb_reset_jump_mips diff --git a/qemu/mips64.h b/qemu/mips64.h index 61de30fee0..2d20e9a52c 100644 --- a/qemu/mips64.h +++ b/qemu/mips64.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _mips64 #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_mips64 #define gen_helper_check_exit_request gen_helper_check_exit_request_mips64 +#define gen_helper_exit_request gen_helper_exit_request_mips64 +#define helper_check_counted_entry helper_check_counted_entry_mips64 +#define helper_check_exit_request helper_check_exit_request_mips64 +#define helper_exit_request helper_exit_request_mips64 +#define helper_uc_tracecode_single helper_uc_tracecode_single_mips64 +#define cpu_restore_icount cpu_restore_icount_mips64 +#define cpu_tcg_exit_request cpu_tcg_exit_request_mips64 #define unicorn_fill_tlb unicorn_fill_tlb_mips64 #define reg_read reg_read_mips64 #define reg_write reg_write_mips64 @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_mips64 #define flatview_translate flatview_translate_mips64 #define flatview_copy flatview_copy_mips64 +#define flatview_reserve flatview_reserve_mips64 +#define address_space_restore_flatview address_space_restore_flatview_mips64 #define address_space_translate_for_iotlb address_space_translate_for_iotlb_mips64 #define qemu_get_cpu qemu_get_cpu_mips64 #define cpu_address_space_init cpu_address_space_init_mips64 @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_mips64 #define memory_moveout memory_moveout_mips64 #define memory_movein memory_movein_mips64 +#define memory_mapping_restore_topology memory_mapping_restore_topology_mips64 +#define memory_mapping_free memory_mapping_free_mips64 +#define memory_mapping_prune memory_mapping_prune_mips64 +#define memory_mapping_normalize memory_mapping_normalize_mips64 #define memory_free memory_free_mips64 #define flatview_unref flatview_unref_mips64 #define address_space_get_flatview address_space_get_flatview_mips64 @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_mips64 #define tb_cleanup tb_cleanup_mips64 #define tb_flush tb_flush_mips64 +#define tb_flush_jit tb_flush_jit_mips64 #define tb_phys_invalidate tb_phys_invalidate_mips64 #define tb_gen_code tb_gen_code_mips64 #define tb_reset_jump tb_reset_jump_mips64 diff --git a/qemu/mips64el.h b/qemu/mips64el.h index a75a6287cf..ff15f0ca15 100644 --- a/qemu/mips64el.h +++ b/qemu/mips64el.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _mips64el #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_mips64el #define gen_helper_check_exit_request gen_helper_check_exit_request_mips64el +#define gen_helper_exit_request gen_helper_exit_request_mips64el +#define helper_check_counted_entry helper_check_counted_entry_mips64el +#define helper_check_exit_request helper_check_exit_request_mips64el +#define helper_exit_request helper_exit_request_mips64el +#define helper_uc_tracecode_single helper_uc_tracecode_single_mips64el +#define cpu_restore_icount cpu_restore_icount_mips64el +#define cpu_tcg_exit_request cpu_tcg_exit_request_mips64el #define unicorn_fill_tlb unicorn_fill_tlb_mips64el #define reg_read reg_read_mips64el #define reg_write reg_write_mips64el @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_mips64el #define flatview_translate flatview_translate_mips64el #define flatview_copy flatview_copy_mips64el +#define flatview_reserve flatview_reserve_mips64el +#define address_space_restore_flatview address_space_restore_flatview_mips64el #define address_space_translate_for_iotlb address_space_translate_for_iotlb_mips64el #define qemu_get_cpu qemu_get_cpu_mips64el #define cpu_address_space_init cpu_address_space_init_mips64el @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_mips64el #define memory_moveout memory_moveout_mips64el #define memory_movein memory_movein_mips64el +#define memory_mapping_restore_topology memory_mapping_restore_topology_mips64el +#define memory_mapping_free memory_mapping_free_mips64el +#define memory_mapping_prune memory_mapping_prune_mips64el +#define memory_mapping_normalize memory_mapping_normalize_mips64el #define memory_free memory_free_mips64el #define flatview_unref flatview_unref_mips64el #define address_space_get_flatview address_space_get_flatview_mips64el @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_mips64el #define tb_cleanup tb_cleanup_mips64el #define tb_flush tb_flush_mips64el +#define tb_flush_jit tb_flush_jit_mips64el #define tb_phys_invalidate tb_phys_invalidate_mips64el #define tb_gen_code tb_gen_code_mips64el #define tb_reset_jump tb_reset_jump_mips64el diff --git a/qemu/mipsel.h b/qemu/mipsel.h index 563acf98b0..9b1b0e97f7 100644 --- a/qemu/mipsel.h +++ b/qemu/mipsel.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _mipsel #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_mipsel #define gen_helper_check_exit_request gen_helper_check_exit_request_mipsel +#define gen_helper_exit_request gen_helper_exit_request_mipsel +#define helper_check_counted_entry helper_check_counted_entry_mipsel +#define helper_check_exit_request helper_check_exit_request_mipsel +#define helper_exit_request helper_exit_request_mipsel +#define helper_uc_tracecode_single helper_uc_tracecode_single_mipsel +#define cpu_restore_icount cpu_restore_icount_mipsel +#define cpu_tcg_exit_request cpu_tcg_exit_request_mipsel #define unicorn_fill_tlb unicorn_fill_tlb_mipsel #define reg_read reg_read_mipsel #define reg_write reg_write_mipsel @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_mipsel #define flatview_translate flatview_translate_mipsel #define flatview_copy flatview_copy_mipsel +#define flatview_reserve flatview_reserve_mipsel +#define address_space_restore_flatview address_space_restore_flatview_mipsel #define address_space_translate_for_iotlb address_space_translate_for_iotlb_mipsel #define qemu_get_cpu qemu_get_cpu_mipsel #define cpu_address_space_init cpu_address_space_init_mipsel @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_mipsel #define memory_moveout memory_moveout_mipsel #define memory_movein memory_movein_mipsel +#define memory_mapping_restore_topology memory_mapping_restore_topology_mipsel +#define memory_mapping_free memory_mapping_free_mipsel +#define memory_mapping_prune memory_mapping_prune_mipsel +#define memory_mapping_normalize memory_mapping_normalize_mipsel #define memory_free memory_free_mipsel #define flatview_unref flatview_unref_mipsel #define address_space_get_flatview address_space_get_flatview_mipsel @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_mipsel #define tb_cleanup tb_cleanup_mipsel #define tb_flush tb_flush_mipsel +#define tb_flush_jit tb_flush_jit_mipsel #define tb_phys_invalidate tb_phys_invalidate_mipsel #define tb_gen_code tb_gen_code_mipsel #define tb_reset_jump tb_reset_jump_mipsel diff --git a/qemu/ppc.h b/qemu/ppc.h index 0a1ecae8b5..3313b2f355 100644 --- a/qemu/ppc.h +++ b/qemu/ppc.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _ppc #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_ppc #define gen_helper_check_exit_request gen_helper_check_exit_request_ppc +#define gen_helper_exit_request gen_helper_exit_request_ppc +#define helper_check_counted_entry helper_check_counted_entry_ppc +#define helper_check_exit_request helper_check_exit_request_ppc +#define helper_exit_request helper_exit_request_ppc +#define helper_uc_tracecode_single helper_uc_tracecode_single_ppc +#define cpu_restore_icount cpu_restore_icount_ppc +#define cpu_tcg_exit_request cpu_tcg_exit_request_ppc #define unicorn_fill_tlb unicorn_fill_tlb_ppc #define reg_read reg_read_ppc #define reg_write reg_write_ppc @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_ppc #define flatview_translate flatview_translate_ppc #define flatview_copy flatview_copy_ppc +#define flatview_reserve flatview_reserve_ppc +#define address_space_restore_flatview address_space_restore_flatview_ppc #define address_space_translate_for_iotlb address_space_translate_for_iotlb_ppc #define qemu_get_cpu qemu_get_cpu_ppc #define cpu_address_space_init cpu_address_space_init_ppc @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_ppc #define memory_moveout memory_moveout_ppc #define memory_movein memory_movein_ppc +#define memory_mapping_restore_topology memory_mapping_restore_topology_ppc +#define memory_mapping_free memory_mapping_free_ppc +#define memory_mapping_prune memory_mapping_prune_ppc +#define memory_mapping_normalize memory_mapping_normalize_ppc #define memory_free memory_free_ppc #define flatview_unref flatview_unref_ppc #define address_space_get_flatview address_space_get_flatview_ppc @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_ppc #define tb_cleanup tb_cleanup_ppc #define tb_flush tb_flush_ppc +#define tb_flush_jit tb_flush_jit_ppc #define tb_phys_invalidate tb_phys_invalidate_ppc #define tb_gen_code tb_gen_code_ppc #define tb_reset_jump tb_reset_jump_ppc diff --git a/qemu/ppc64.h b/qemu/ppc64.h index 4557c76981..2d3e777353 100644 --- a/qemu/ppc64.h +++ b/qemu/ppc64.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _ppc64 #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_ppc64 #define gen_helper_check_exit_request gen_helper_check_exit_request_ppc64 +#define gen_helper_exit_request gen_helper_exit_request_ppc64 +#define helper_check_counted_entry helper_check_counted_entry_ppc64 +#define helper_check_exit_request helper_check_exit_request_ppc64 +#define helper_exit_request helper_exit_request_ppc64 +#define helper_uc_tracecode_single helper_uc_tracecode_single_ppc64 +#define cpu_restore_icount cpu_restore_icount_ppc64 +#define cpu_tcg_exit_request cpu_tcg_exit_request_ppc64 #define unicorn_fill_tlb unicorn_fill_tlb_ppc64 #define reg_read reg_read_ppc64 #define reg_write reg_write_ppc64 @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_ppc64 #define flatview_translate flatview_translate_ppc64 #define flatview_copy flatview_copy_ppc64 +#define flatview_reserve flatview_reserve_ppc64 +#define address_space_restore_flatview address_space_restore_flatview_ppc64 #define address_space_translate_for_iotlb address_space_translate_for_iotlb_ppc64 #define qemu_get_cpu qemu_get_cpu_ppc64 #define cpu_address_space_init cpu_address_space_init_ppc64 @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_ppc64 #define memory_moveout memory_moveout_ppc64 #define memory_movein memory_movein_ppc64 +#define memory_mapping_restore_topology memory_mapping_restore_topology_ppc64 +#define memory_mapping_free memory_mapping_free_ppc64 +#define memory_mapping_prune memory_mapping_prune_ppc64 +#define memory_mapping_normalize memory_mapping_normalize_ppc64 #define memory_free memory_free_ppc64 #define flatview_unref flatview_unref_ppc64 #define address_space_get_flatview address_space_get_flatview_ppc64 @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_ppc64 #define tb_cleanup tb_cleanup_ppc64 #define tb_flush tb_flush_ppc64 +#define tb_flush_jit tb_flush_jit_ppc64 #define tb_phys_invalidate tb_phys_invalidate_ppc64 #define tb_gen_code tb_gen_code_ppc64 #define tb_reset_jump tb_reset_jump_ppc64 diff --git a/qemu/riscv32.h b/qemu/riscv32.h index 99ade7dc0b..e7eade5d50 100644 --- a/qemu/riscv32.h +++ b/qemu/riscv32.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _riscv32 #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_riscv32 #define gen_helper_check_exit_request gen_helper_check_exit_request_riscv32 +#define gen_helper_exit_request gen_helper_exit_request_riscv32 +#define helper_check_counted_entry helper_check_counted_entry_riscv32 +#define helper_check_exit_request helper_check_exit_request_riscv32 +#define helper_exit_request helper_exit_request_riscv32 +#define helper_uc_tracecode_single helper_uc_tracecode_single_riscv32 +#define cpu_restore_icount cpu_restore_icount_riscv32 +#define cpu_tcg_exit_request cpu_tcg_exit_request_riscv32 #define unicorn_fill_tlb unicorn_fill_tlb_riscv32 #define reg_read reg_read_riscv32 #define reg_write reg_write_riscv32 @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_riscv32 #define flatview_translate flatview_translate_riscv32 #define flatview_copy flatview_copy_riscv32 +#define flatview_reserve flatview_reserve_riscv32 +#define address_space_restore_flatview address_space_restore_flatview_riscv32 #define address_space_translate_for_iotlb address_space_translate_for_iotlb_riscv32 #define qemu_get_cpu qemu_get_cpu_riscv32 #define cpu_address_space_init cpu_address_space_init_riscv32 @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_riscv32 #define memory_moveout memory_moveout_riscv32 #define memory_movein memory_movein_riscv32 +#define memory_mapping_restore_topology memory_mapping_restore_topology_riscv32 +#define memory_mapping_free memory_mapping_free_riscv32 +#define memory_mapping_prune memory_mapping_prune_riscv32 +#define memory_mapping_normalize memory_mapping_normalize_riscv32 #define memory_free memory_free_riscv32 #define flatview_unref flatview_unref_riscv32 #define address_space_get_flatview address_space_get_flatview_riscv32 @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_riscv32 #define tb_cleanup tb_cleanup_riscv32 #define tb_flush tb_flush_riscv32 +#define tb_flush_jit tb_flush_jit_riscv32 #define tb_phys_invalidate tb_phys_invalidate_riscv32 #define tb_gen_code tb_gen_code_riscv32 #define tb_reset_jump tb_reset_jump_riscv32 @@ -2109,6 +2124,7 @@ #define riscv_cpu_debug_check_breakpoint riscv_cpu_debug_check_breakpoint_riscv32 #define riscv_cpu_debug_check_watchpoint riscv_cpu_debug_check_watchpoint_riscv32 #define riscv_trigger_init riscv_trigger_init_riscv32 +#define riscv_trigger_update_all riscv_trigger_update_all_riscv32 #define riscv_cpu_get_fflags riscv_cpu_get_fflags_riscv32 #define riscv_cpu_set_fflags riscv_cpu_set_fflags_riscv32 #define helper_set_rounding_mode helper_set_rounding_mode_riscv32 diff --git a/qemu/riscv64.h b/qemu/riscv64.h index 5b7aa453a0..925ce8daab 100644 --- a/qemu/riscv64.h +++ b/qemu/riscv64.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _riscv64 #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_riscv64 #define gen_helper_check_exit_request gen_helper_check_exit_request_riscv64 +#define gen_helper_exit_request gen_helper_exit_request_riscv64 +#define helper_check_counted_entry helper_check_counted_entry_riscv64 +#define helper_check_exit_request helper_check_exit_request_riscv64 +#define helper_exit_request helper_exit_request_riscv64 +#define helper_uc_tracecode_single helper_uc_tracecode_single_riscv64 +#define cpu_restore_icount cpu_restore_icount_riscv64 +#define cpu_tcg_exit_request cpu_tcg_exit_request_riscv64 #define unicorn_fill_tlb unicorn_fill_tlb_riscv64 #define reg_read reg_read_riscv64 #define reg_write reg_write_riscv64 @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_riscv64 #define flatview_translate flatview_translate_riscv64 #define flatview_copy flatview_copy_riscv64 +#define flatview_reserve flatview_reserve_riscv64 +#define address_space_restore_flatview address_space_restore_flatview_riscv64 #define address_space_translate_for_iotlb address_space_translate_for_iotlb_riscv64 #define qemu_get_cpu qemu_get_cpu_riscv64 #define cpu_address_space_init cpu_address_space_init_riscv64 @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_riscv64 #define memory_moveout memory_moveout_riscv64 #define memory_movein memory_movein_riscv64 +#define memory_mapping_restore_topology memory_mapping_restore_topology_riscv64 +#define memory_mapping_free memory_mapping_free_riscv64 +#define memory_mapping_prune memory_mapping_prune_riscv64 +#define memory_mapping_normalize memory_mapping_normalize_riscv64 #define memory_free memory_free_riscv64 #define flatview_unref flatview_unref_riscv64 #define address_space_get_flatview address_space_get_flatview_riscv64 @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_riscv64 #define tb_cleanup tb_cleanup_riscv64 #define tb_flush tb_flush_riscv64 +#define tb_flush_jit tb_flush_jit_riscv64 #define tb_phys_invalidate tb_phys_invalidate_riscv64 #define tb_gen_code tb_gen_code_riscv64 #define tb_reset_jump tb_reset_jump_riscv64 @@ -2109,6 +2124,7 @@ #define riscv_cpu_debug_check_breakpoint riscv_cpu_debug_check_breakpoint_riscv64 #define riscv_cpu_debug_check_watchpoint riscv_cpu_debug_check_watchpoint_riscv64 #define riscv_trigger_init riscv_trigger_init_riscv64 +#define riscv_trigger_update_all riscv_trigger_update_all_riscv64 #define riscv_cpu_get_fflags riscv_cpu_get_fflags_riscv64 #define riscv_cpu_set_fflags riscv_cpu_set_fflags_riscv64 #define helper_set_rounding_mode helper_set_rounding_mode_riscv64 diff --git a/qemu/s390x.h b/qemu/s390x.h index c51ea1b8df..2d50641658 100644 --- a/qemu/s390x.h +++ b/qemu/s390x.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _s390x #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_s390x #define gen_helper_check_exit_request gen_helper_check_exit_request_s390x +#define gen_helper_exit_request gen_helper_exit_request_s390x +#define helper_check_counted_entry helper_check_counted_entry_s390x +#define helper_check_exit_request helper_check_exit_request_s390x +#define helper_exit_request helper_exit_request_s390x +#define helper_uc_tracecode_single helper_uc_tracecode_single_s390x +#define cpu_restore_icount cpu_restore_icount_s390x +#define cpu_tcg_exit_request cpu_tcg_exit_request_s390x #define unicorn_fill_tlb unicorn_fill_tlb_s390x #define reg_read reg_read_s390x #define reg_write reg_write_s390x @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_s390x #define flatview_translate flatview_translate_s390x #define flatview_copy flatview_copy_s390x +#define flatview_reserve flatview_reserve_s390x +#define address_space_restore_flatview address_space_restore_flatview_s390x #define address_space_translate_for_iotlb address_space_translate_for_iotlb_s390x #define qemu_get_cpu qemu_get_cpu_s390x #define cpu_address_space_init cpu_address_space_init_s390x @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_s390x #define memory_moveout memory_moveout_s390x #define memory_movein memory_movein_s390x +#define memory_mapping_restore_topology memory_mapping_restore_topology_s390x +#define memory_mapping_free memory_mapping_free_s390x +#define memory_mapping_prune memory_mapping_prune_s390x +#define memory_mapping_normalize memory_mapping_normalize_s390x #define memory_free memory_free_s390x #define flatview_unref flatview_unref_s390x #define address_space_get_flatview address_space_get_flatview_s390x @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_s390x #define tb_cleanup tb_cleanup_s390x #define tb_flush tb_flush_s390x +#define tb_flush_jit tb_flush_jit_s390x #define tb_phys_invalidate tb_phys_invalidate_s390x #define tb_gen_code tb_gen_code_s390x #define tb_reset_jump tb_reset_jump_s390x diff --git a/qemu/softmmu/cpus.c b/qemu/softmmu/cpus.c index 6207dd5da5..8bf3543487 100644 --- a/qemu/softmmu/cpus.c +++ b/qemu/softmmu/cpus.c @@ -97,16 +97,15 @@ static int tcg_cpu_exec(struct uc_struct *uc) r = cpu_exec(uc, cpu); // quit current TB but continue emulating? - if (uc->quit_request && !uc->stop_request) { + if (uc->quit_request && !uc_stop_requested(uc)) { // reset stop_request - uc->stop_request = false; + uc_set_stop_request(uc, false); // resume cpu cpu->halted = 0; - cpu->exit_request = 0; cpu->exception_index = -1; cpu_resume(cpu); - } else if (uc->stop_request) { + } else if (uc_stop_requested(uc)) { //printf(">>> got STOP request!!!\n"); finish = true; break; @@ -135,9 +134,7 @@ static int tcg_cpu_exec(struct uc_struct *uc) } } uc->exit_request = 0; - uc->cpu->exit_request = 0; uc->cpu->icount_decr_ptr->u16.high = 0; - uc->cpu->tcg_exit_req = 0; return finish; } @@ -212,7 +209,6 @@ void resume_all_vcpus(struct uc_struct* uc) { CPUState *cpu = uc->cpu; cpu->halted = 0; - cpu->exit_request = 0; cpu->exception_index = -1; cpu_resume(cpu); /* static void qemu_tcg_cpu_loop(struct uc_struct *uc) */ diff --git a/qemu/softmmu/ioport.c b/qemu/softmmu/ioport.c index fdf8d0f457..da4235f4bb 100644 --- a/qemu/softmmu/ioport.c +++ b/qemu/softmmu/ioport.c @@ -50,6 +50,7 @@ void cpu_outb(struct uc_struct *uc, uint32_t addr, uint8_t val, uintptr_t retadd cpu_restore_state(uc->cpu, retaddr, false); synced = true; } + uc->tb_exec_frame_publish(uc, retaddr); JIT_CALLBACK_GUARD(((uc_cb_insn_out_t)hook->callback)(uc, addr, 1, val, hook->user_data)); } } @@ -76,6 +77,7 @@ void cpu_outw(struct uc_struct *uc, uint32_t addr, uint16_t val, uintptr_t retad cpu_restore_state(uc->cpu, retaddr, false); synced = true; } + uc->tb_exec_frame_publish(uc, retaddr); JIT_CALLBACK_GUARD(((uc_cb_insn_out_t)hook->callback)(uc, addr, 2, val, hook->user_data)); } } @@ -102,6 +104,7 @@ void cpu_outl(struct uc_struct *uc, uint32_t addr, uint32_t val, uintptr_t retad cpu_restore_state(uc->cpu, retaddr, false); synced = true; } + uc->tb_exec_frame_publish(uc, retaddr); JIT_CALLBACK_GUARD(((uc_cb_insn_out_t)hook->callback)(uc, addr, 4, val, hook->user_data)); } } @@ -128,6 +131,7 @@ uint8_t cpu_inb(struct uc_struct *uc, uint32_t addr, uintptr_t retaddr) synced = true; } uint8_t ret; + uc->tb_exec_frame_publish(uc, retaddr); JIT_CALLBACK_GUARD_VAR(ret, ((uc_cb_insn_in_t)hook->callback)(uc, addr, 1, hook->user_data)); return ret; } @@ -158,6 +162,7 @@ uint16_t cpu_inw(struct uc_struct *uc, uint32_t addr, uintptr_t retaddr) synced = true; } uint16_t ret; + uc->tb_exec_frame_publish(uc, retaddr); JIT_CALLBACK_GUARD_VAR(ret, ((uc_cb_insn_in_t)hook->callback)(uc, addr, 2, hook->user_data)); return ret; } @@ -190,6 +195,7 @@ uint32_t cpu_inl(struct uc_struct *uc, uint32_t addr, uintptr_t retaddr) synced = true; } uint32_t ret; + uc->tb_exec_frame_publish(uc, retaddr); JIT_CALLBACK_GUARD_VAR(ret, ((uc_cb_insn_in_t)hook->callback)(uc, addr, 4, hook->user_data)); return ret; } diff --git a/qemu/softmmu/memory.c b/qemu/softmmu/memory.c index c2d4244504..922a88515e 100644 --- a/qemu/softmmu/memory.c +++ b/qemu/softmmu/memory.c @@ -24,13 +24,15 @@ #include "exec/exec-all.h" #include "uc_priv.h" -//#define DEBUG_UNASSIGNED +// #define DEBUG_UNASSIGNED #define MTE_TAG_STORAGE_GRANULE 32 void memory_region_transaction_begin(void); static void memory_region_transaction_commit(MemoryRegion *mr); static void memory_region_destructor_container(MemoryRegion *mr); +static void flatview_unref_notify(gpointer data); +static inline void flatview_ref(FlatView *view); typedef struct AddrRange AddrRange; @@ -43,8 +45,20 @@ struct AddrRange { Int128 size; }; +/* Range of memory in the global map. Addresses are absolute. */ +struct FlatRange { + MemoryRegion *mr; + hwaddr offset_in_region; + AddrRange addr; + bool readonly; +}; + +#define FOR_EACH_FLAT_RANGE(var, view) \ + for (var = (view)->ranges; var < (view)->ranges + (view)->nr; ++var) + // Unicorn engine -MemoryRegion *memory_map(struct uc_struct *uc, hwaddr begin, size_t size, uint32_t perms) +MemoryRegion *memory_map(struct uc_struct *uc, hwaddr begin, size_t size, + uint32_t perms) { MemoryRegion *ram = g_new(MemoryRegion, 1); @@ -55,7 +69,8 @@ MemoryRegion *memory_map(struct uc_struct *uc, hwaddr begin, size_t size, uint32 return NULL; } - memory_region_add_subregion_overlap(uc->system_memory, begin, ram, uc->snapshot_level); + memory_region_add_subregion_overlap(uc->system_memory, begin, ram, + uc->snapshot_level); if (uc->cpu) { tlb_flush(uc->cpu); @@ -64,7 +79,8 @@ MemoryRegion *memory_map(struct uc_struct *uc, hwaddr begin, size_t size, uint32 return ram; } -MemoryRegion *memory_map_ptr(struct uc_struct *uc, hwaddr begin, size_t size, uint32_t perms, void *ptr) +MemoryRegion *memory_map_ptr(struct uc_struct *uc, hwaddr begin, size_t size, + uint32_t perms, void *ptr) { MemoryRegion *ram = g_new(MemoryRegion, 1); @@ -76,7 +92,8 @@ MemoryRegion *memory_map_ptr(struct uc_struct *uc, hwaddr begin, size_t size, ui return NULL; } - memory_region_add_subregion(uc->system_memory, begin, ram); + memory_region_add_subregion_overlap(uc->system_memory, begin, ram, + uc->snapshot_level); if (uc->cpu) { tlb_flush(uc->cpu); @@ -85,15 +102,21 @@ MemoryRegion *memory_map_ptr(struct uc_struct *uc, hwaddr begin, size_t size, ui return ram; } -static void make_contained(struct uc_struct *uc, MemoryRegion *current) +static void make_contained(struct uc_struct *uc, UcMapping *mapping, + MemoryRegion *current) { - hwaddr addr = current->addr; MemoryRegion *container = g_new(MemoryRegion, 1); + + assert(mapping->root == current); memory_region_init(uc, container, int128_get64(current->size)); container->destructor = memory_region_destructor_container; + container->uc_mapping = mapping; memory_region_del_subregion(uc->system_memory, current); - memory_region_add_subregion_overlap(container, 0, current, current->priority); - memory_region_add_subregion(uc->system_memory, addr, container); + memory_region_add_subregion_overlap(container, 0, current, + current->priority); + memory_region_add_subregion_overlap(uc->system_memory, mapping->begin, + container, mapping->priority); + mapping->root = container; } static void memory_cow_copy_mte_tags(RAMBlock *dst, RAMBlock *src, @@ -112,14 +135,14 @@ static void memory_cow_copy_mte_tags(RAMBlock *dst, RAMBlock *src, tag_size = DIV_ROUND_UP(size, MTE_TAG_STORAGE_GRANULE); tag_size = MIN(tag_size, src->mte_tags_size - src_tag_offset); - dst->mte_tags_size = DIV_ROUND_UP(dst->max_length, - MTE_TAG_STORAGE_GRANULE); + dst->mte_tags_size = DIV_ROUND_UP(dst->max_length, MTE_TAG_STORAGE_GRANULE); dst->mte_tags = g_malloc0(dst->mte_tags_size); memcpy(dst->mte_tags, src->mte_tags + src_tag_offset, MIN(tag_size, dst->mte_tags_size)); } -MemoryRegion *memory_cow(struct uc_struct *uc, MemoryRegion *current, hwaddr begin, size_t size) +MemoryRegion *memory_cow(struct uc_struct *uc, UcMapping *mapping, + MemoryRegion *current, hwaddr begin, size_t size) { hwaddr addr; hwaddr offset; @@ -129,11 +152,12 @@ MemoryRegion *memory_cow(struct uc_struct *uc, MemoryRegion *current, hwaddr beg assert((begin & ~TARGET_PAGE_MASK) == 0); assert((size & ~TARGET_PAGE_MASK) == 0); - if (current->container == uc->system_memory) { - make_contained(uc, current); + assert(current->uc_mapping == mapping); + if (mapping->root == current) { + make_contained(uc, mapping, current); } - offset = begin - current->container->addr;; - current_offset = offset - current->addr; + offset = begin - mapping->begin; + current_offset = offset - current->mapping_offset; memory_region_init_ram(uc, ram, size, current->perms); if (ram->addr == -1 || !ram->ram_block) { @@ -141,26 +165,33 @@ MemoryRegion *memory_cow(struct uc_struct *uc, MemoryRegion *current, hwaddr beg return NULL; } - memcpy(ramblock_ptr(ram->ram_block, 0), ramblock_ptr(current->ram_block, current_offset), size); - memory_cow_copy_mte_tags(ram->ram_block, current->ram_block, - current_offset, size); - memory_region_add_subregion_overlap(current->container, offset, ram, uc->snapshot_level); + memcpy(ramblock_ptr(ram->ram_block, 0), + ramblock_ptr(current->ram_block, current_offset), size); + memory_cow_copy_mte_tags(ram->ram_block, current->ram_block, current_offset, + size); + ram->uc_mapping = mapping; + ram->mapping_offset = offset; + ram->mapping_next = mapping->regions; + mapping->regions = ram; + memory_region_add_subregion_overlap(mapping->root, offset, ram, + uc->snapshot_level); if (uc->cpu) { - for (addr = ram->addr; (int64_t)(ram->end - addr) > 0; addr += uc->target_page_size) { - tlb_flush_page(uc->cpu, addr); + for (addr = begin; addr - begin < size; addr += uc->target_page_size) { + tlb_flush_page(uc->cpu, addr); } } return ram; } -static uint64_t mmio_read_wrapper(struct uc_struct *uc, void *opaque, hwaddr addr, unsigned size) +static uint64_t mmio_read_wrapper(struct uc_struct *uc, void *opaque, + hwaddr addr, unsigned size) { - mmio_cbs* cbs = (mmio_cbs*)opaque; + mmio_cbs *cbs = (mmio_cbs *)opaque; // We have to care about 32bit target. - addr = addr & ( (target_ulong)(-1) ); + addr = addr & ((target_ulong)(-1)); if (cbs->read) { return cbs->read(uc, addr, size, cbs->user_data_read); } else { @@ -168,12 +199,13 @@ static uint64_t mmio_read_wrapper(struct uc_struct *uc, void *opaque, hwaddr add } } -static void mmio_write_wrapper(struct uc_struct *uc, void *opaque, hwaddr addr, uint64_t data, unsigned size) +static void mmio_write_wrapper(struct uc_struct *uc, void *opaque, hwaddr addr, + uint64_t data, unsigned size) { - mmio_cbs* cbs = (mmio_cbs*)opaque; - + mmio_cbs *cbs = (mmio_cbs *)opaque; + // We have to care about 32bit target. - addr = addr & ( (target_ulong)(-1) ); + addr = addr & ((target_ulong)(-1)); if (cbs->write) { cbs->write(uc, addr, size, data, cbs->user_data_write); } @@ -185,11 +217,12 @@ static void mmio_region_destructor_uc(MemoryRegion *mr) } MemoryRegion *memory_map_io(struct uc_struct *uc, ram_addr_t begin, size_t size, - uc_cb_mmio_read_t read_cb, uc_cb_mmio_write_t write_cb, - void *user_data_read, void *user_data_write) + uc_cb_mmio_read_t read_cb, + uc_cb_mmio_write_t write_cb, void *user_data_read, + void *user_data_write) { MemoryRegion *mmio = g_new(MemoryRegion, 1); - mmio_cbs* opaques = g_new(mmio_cbs, 1); + mmio_cbs *opaques = g_new(mmio_cbs, 1); MemoryRegionOps *ops = &opaques->ops; opaques->read = read_cb; opaques->write = write_cb; @@ -216,7 +249,8 @@ MemoryRegion *memory_map_io(struct uc_struct *uc, ram_addr_t begin, size_t size, if (write_cb) mmio->perms |= UC_PROT_WRITE; - memory_region_add_subregion(uc->system_memory, begin, mmio); + memory_region_add_subregion_overlap(uc->system_memory, begin, mmio, + uc->snapshot_level); if (uc->cpu) tlb_flush(uc->cpu); @@ -225,7 +259,7 @@ MemoryRegion *memory_map_io(struct uc_struct *uc, ram_addr_t begin, size_t size, } static void memory_region_remove_subregion(MemoryRegion *mr, - MemoryRegion *subregion) + MemoryRegion *subregion) { assert(subregion->container == mr); subregion->container = NULL; @@ -244,7 +278,9 @@ void memory_region_filter_subregions(MemoryRegion *mr, int32_t level) * the context restore case the flatview is taken from the context so * no need to rebuild it. */ - QTAILQ_FOREACH_SAFE(subregion, &mr->subregions, subregions_link, subregion_next) { + QTAILQ_FOREACH_SAFE(subregion, &mr->subregions, subregions_link, + subregion_next) + { if (subregion->priority >= level) { memory_region_remove_subregion(mr, subregion); subregion->destructor(subregion); @@ -253,83 +289,236 @@ void memory_region_filter_subregions(MemoryRegion *mr, int32_t level) } } -static void memory_region_remove_mapped_block(struct uc_struct *uc, MemoryRegion *mr, bool free) +static void memory_region_insert_subregion(MemoryRegion *mr, + MemoryRegion *subregion) { - size_t i; - for (i = 0; i < uc->mapped_block_count; i++) { - if (uc->mapped_blocks[i] == mr) { - uc->mapped_block_count--; - //shift remainder of array down over deleted pointer - memmove(&uc->mapped_blocks[i], &uc->mapped_blocks[i + 1], sizeof(MemoryRegion*) * (uc->mapped_block_count - i)); - if (free) { - mr->destructor(mr); - g_free(mr); - } - break; + MemoryRegion *other; + + QTAILQ_FOREACH(other, &mr->subregions, subregions_link) + { + if (subregion->priority > other->priority) { + QTAILQ_INSERT_BEFORE(other, subregion, subregions_link); + return; } } + QTAILQ_INSERT_TAIL(&mr->subregions, subregion, subregions_link); } -void memory_moveout(struct uc_struct *uc, MemoryRegion *mr) +static void memory_region_add_subregion_raw(MemoryRegion *mr, hwaddr offset, + MemoryRegion *subregion, + int32_t priority) { - hwaddr addr; - /* A bit dirty, but it works. - * The first subregion will be the one with the smalest priority. - * In case of CoW this will always be the region which is mapped initial and later be moved in the subregion of the container. - * The initial subregion is the one stored in mapped_blocks - * Because CoW is done after the snapshot level is increased there is only on subregion with - */ - memory_region_transaction_begin(); - MemoryRegion *mr_block = QTAILQ_FIRST(&mr->subregions); + assert(!subregion->container); + subregion->container = mr; + subregion->addr = offset; + subregion->end = offset + int128_get64(subregion->size); + subregion->priority = priority; + memory_region_insert_subregion(mr, subregion); +} - if (!mr_block) { - mr_block = mr; - } +static void memory_mapping_invalidate(struct uc_struct *uc, UcMapping *mapping) +{ + hwaddr offset; if (uc->cpu) { - // We also need to remove all tb cache - uc->uc_invalidate_tb(uc, mr->addr, int128_get64(mr->size)); + uc->uc_invalidate_tb(uc, mapping->begin, mapping->size); - // Make sure all pages associated with the MemoryRegion are flushed - // Only need to do this if we are in a running state - for (addr = mr->addr; (int64_t)(mr->end - addr) > 0; addr += uc->target_page_size) { - tlb_flush_page(uc->cpu, addr); + for (offset = 0; offset < mapping->size; + offset += uc->target_page_size) { + tlb_flush_page(uc->cpu, mapping->begin + offset); } } +} - memory_region_del_subregion(uc->system_memory, mr); - g_array_append_val(uc->unmapped_regions, mr); - memory_region_remove_mapped_block(uc, mr_block, false); - uc->memory_region_update_pending = true; - memory_region_transaction_commit(uc->system_memory); - /* dirty hack to save the snapshot level */ - mr->container = (void *)(intptr_t)uc->snapshot_level; +void memory_moveout(struct uc_struct *uc, UcMapping *mapping, + bool update_topology) +{ + MemoryRegion *root = mapping->root; + + assert(root->container == uc->system_memory); + memory_mapping_invalidate(uc, mapping); + + if (update_topology) { + memory_region_del_subregion(uc->system_memory, root); + } else { + memory_region_remove_subregion(uc->system_memory, root); + } } -void memory_movein(struct uc_struct *uc, MemoryRegion *mr) +void memory_movein(struct uc_struct *uc, UcMapping *mapping, + bool update_topology) { - memory_region_transaction_begin(); - memory_region_add_subregion_overlap(uc->system_memory, mr->addr, mr, mr->priority); - uc->memory_region_update_pending = true; - memory_region_transaction_commit(uc->system_memory); + MemoryRegion *root = mapping->root; + + assert(!root->container); + if (update_topology) { + memory_region_add_subregion_overlap(uc->system_memory, mapping->begin, + root, mapping->priority); + } else { + memory_region_add_subregion_raw(uc->system_memory, mapping->begin, root, + mapping->priority); + } } -void memory_unmap(struct uc_struct *uc, MemoryRegion *mr) +void memory_mapping_restore_topology(struct uc_struct *uc, UcMapping *mapping, + MemoryRegion *const *regions, + uint32_t region_count, + bool update_topology) { - hwaddr addr; + MemoryRegion *root = mapping->root; + MemoryRegion *region, *next; + uint32_t i; + bool root_active; - if (uc->cpu) { - // We also need to remove all tb cache - uc->uc_invalidate_tb(uc, mr->addr, int128_get64(mr->size)); + if (root->terminates) { + assert(region_count == 1 && regions[0] == root); + return; + } + + assert(region_count != 0); + root_active = root->container == uc->system_memory; + QTAILQ_FOREACH_SAFE(region, &root->subregions, subregions_link, next) + { + memory_region_remove_subregion(root, region); + } + + for (i = 0; i < region_count; i++) { + region = regions[i]; + assert(region->uc_mapping == mapping); + memory_region_add_subregion_raw(root, region->mapping_offset, region, + region->priority); + } + + if (update_topology && root_active) { + uc->memory_region_update_pending = true; + memory_region_transaction_commit(root); + } +} + +static void memory_region_copy_mte_tags(MemoryRegion *destination, + hwaddr destination_offset, + MemoryRegion *source, + hwaddr source_offset, hwaddr size) +{ + RAMBlock *destination_block = destination->ram_block; + RAMBlock *source_block = source->ram_block; + ram_addr_t destination_tag_offset; + ram_addr_t source_tag_offset; + ram_addr_t tag_count; - // Make sure all pages associated with the MemoryRegion are flushed - // Only need to do this if we are in a running state - for (addr = mr->addr; (int64_t)(mr->end - addr) > 0; addr += uc->target_page_size) { - tlb_flush_page(uc->cpu, addr); + destination_tag_offset = destination_offset / MTE_TAG_STORAGE_GRANULE; + source_tag_offset = source_offset / MTE_TAG_STORAGE_GRANULE; + tag_count = DIV_ROUND_UP(size, MTE_TAG_STORAGE_GRANULE); + + if (!source_block->mte_tags) { + if (destination_block->mte_tags) { + memset(destination_block->mte_tags + destination_tag_offset, 0, + tag_count); } + return; + } + if (!destination_block->mte_tags) { + destination_block->mte_tags_size = + DIV_ROUND_UP(destination_block->max_length, + MTE_TAG_STORAGE_GRANULE); + destination_block->mte_tags = + g_malloc0(destination_block->mte_tags_size); } - memory_region_del_subregion(uc->system_memory, mr); - memory_region_remove_mapped_block(uc, mr, true); + + assert(source_tag_offset + tag_count <= source_block->mte_tags_size); + assert(destination_tag_offset + tag_count <= + destination_block->mte_tags_size); + memmove(destination_block->mte_tags + destination_tag_offset, + source_block->mte_tags + source_tag_offset, tag_count); +} + +void memory_mapping_normalize(UcMapping *mapping) +{ + struct uc_struct *uc = mapping->owner; + FlatView *view = uc->address_space_memory.current_map; + MemoryRegion *root = mapping->root; + MemoryRegion *regions = mapping->regions; + MemoryRegion *base = NULL; + MemoryRegion *region; + MemoryRegion *next; + FlatRange *range; + uint64_t covered = 0; + + if (!mapping->active || !root || root->terminates || !view) { + return; + } + + for (region = mapping->regions; region; region = region->mapping_next) { + if (region->ram && region->mapping_offset == 0 && + int128_get64(region->size) == mapping->size) { + base = region; + } + } + if (!base || !base->ram_block) { + return; + } + + FOR_EACH_FLAT_RANGE(range, view) { + MemoryRegion *source = range->mr; + uint64_t range_begin; + uint64_t range_size; + uint64_t destination_offset; + + if (source->uc_mapping != mapping) { + continue; + } + range_begin = int128_get64(range->addr.start); + range_size = int128_get64(range->addr.size); + destination_offset = range_begin - mapping->begin; + if (destination_offset >= mapping->size || + range_size > mapping->size - destination_offset || + range_size > mapping->size - covered || !source->ram_block) { + return; + } + + memmove(ramblock_ptr(base->ram_block, destination_offset), + ramblock_ptr(source->ram_block, range->offset_in_region), + range_size); + memory_region_copy_mte_tags(base, destination_offset, source, + range->offset_in_region, range_size); + covered += range_size; + } + if (covered != mapping->size) { + return; + } + + memory_mapping_invalidate(uc, mapping); + memory_region_del_subregion(uc->system_memory, root); + for (region = mapping->regions; region; region = region->mapping_next) { + if (region->container) { + assert(region->container == root); + memory_region_remove_subregion(root, region); + } + } + + mapping->root = base; + base->mapping_offset = 0; + memory_region_add_subregion_overlap(uc->system_memory, mapping->begin, + base, mapping->priority); + + root->destructor(root); + g_free(root); + for (region = regions; region; region = next) { + next = region->mapping_next; + if (region != base) { + assert(region->context_refs == 0); + assert(!region->container); + region->destructor(region); + g_free(region); + } + } + base->mapping_next = NULL; + mapping->regions = base; +} + +void memory_unmap(struct uc_struct *uc, UcMapping *mapping) +{ + memory_moveout(uc, mapping, true); } int memory_free(struct uc_struct *uc) @@ -337,7 +526,9 @@ int memory_free(struct uc_struct *uc) MemoryRegion *subregion, *subregion_next; MemoryRegion *mr = uc->system_memory; - QTAILQ_FOREACH_SAFE(subregion, &mr->subregions, subregions_link, subregion_next) { + QTAILQ_FOREACH_SAFE(subregion, &mr->subregions, subregions_link, + subregion_next) + { subregion->enabled = false; memory_region_del_subregion(uc->system_memory, subregion); subregion->destructor(subregion); @@ -350,7 +541,7 @@ int memory_free(struct uc_struct *uc) static AddrRange addrrange_make(Int128 start, Int128 size) { - return (AddrRange) { start, size }; + return (AddrRange){start, size}; } static bool addrrange_equal(AddrRange r1, AddrRange r2) @@ -365,14 +556,13 @@ static Int128 addrrange_end(AddrRange r) static bool addrrange_contains(AddrRange range, Int128 addr) { - return int128_ge(addr, range.start) - && int128_lt(addr, addrrange_end(range)); + return int128_ge(addr, range.start) && + int128_lt(addr, addrrange_end(range)); } static bool addrrange_intersects(AddrRange r1, AddrRange r2) { - return addrrange_contains(r1, r2.start) - || addrrange_contains(r2, r1.start); + return addrrange_contains(r1, r2.start) || addrrange_contains(r2, r1.start); } static AddrRange addrrange_intersection(AddrRange r1, AddrRange r2) @@ -384,77 +574,160 @@ static AddrRange addrrange_intersection(AddrRange r1, AddrRange r2) enum ListenerDirection { Forward, Reverse }; -#define MEMORY_LISTENER_CALL_GLOBAL(uc, _callback, _direction) \ - do { \ - MemoryListener *_listener; \ - \ - switch (_direction) { \ - case Forward: \ - QTAILQ_FOREACH(_listener, &uc->memory_listeners, link) { \ - if (_listener->_callback) { \ - _listener->_callback(_listener); \ - } \ - } \ - break; \ - case Reverse: \ - QTAILQ_FOREACH_REVERSE(_listener, &uc->memory_listeners, link) { \ - if (_listener->_callback) { \ - _listener->_callback(_listener); \ - } \ - } \ - break; \ - default: \ - abort(); \ - } \ +#define MEMORY_LISTENER_CALL_GLOBAL(uc, _callback, _direction) \ + do { \ + MemoryListener *_listener; \ + \ + switch (_direction) { \ + case Forward: \ + QTAILQ_FOREACH(_listener, &uc->memory_listeners, link) \ + { \ + if (_listener->_callback) { \ + _listener->_callback(_listener); \ + } \ + } \ + break; \ + case Reverse: \ + QTAILQ_FOREACH_REVERSE(_listener, &uc->memory_listeners, link) \ + { \ + if (_listener->_callback) { \ + _listener->_callback(_listener); \ + } \ + } \ + break; \ + default: \ + abort(); \ + } \ } while (0) -#define MEMORY_LISTENER_CALL(_as, _callback, _direction, _section) \ - do { \ - MemoryListener *_listener; \ - \ - switch (_direction) { \ - case Forward: \ - QTAILQ_FOREACH(_listener, &(_as)->listeners, link_as) { \ - if (_listener->_callback) { \ - _listener->_callback(_listener, _section); \ - } \ - } \ - break; \ - case Reverse: \ - QTAILQ_FOREACH_REVERSE(_listener, &(_as)->listeners, link_as) { \ - if (_listener->_callback) { \ - _listener->_callback(_listener, _section); \ - } \ - } \ - break; \ - default: \ - abort(); \ - } \ +#define MEMORY_LISTENER_CALL(_as, _callback, _direction, _section) \ + do { \ + MemoryListener *_listener; \ + \ + switch (_direction) { \ + case Forward: \ + QTAILQ_FOREACH(_listener, &(_as)->listeners, link_as) \ + { \ + if (_listener->_callback) { \ + _listener->_callback(_listener, _section); \ + } \ + } \ + break; \ + case Reverse: \ + QTAILQ_FOREACH_REVERSE(_listener, &(_as)->listeners, link_as) \ + { \ + if (_listener->_callback) { \ + _listener->_callback(_listener, _section); \ + } \ + } \ + break; \ + default: \ + abort(); \ + } \ } while (0) /* No need to ref/unref .mr, the FlatRange keeps it alive. */ -#define MEMORY_LISTENER_UPDATE_REGION(fr, as, dir, callback) \ - do { \ - MemoryRegionSection mrs = section_from_flat_range(fr, \ - address_space_to_flatview(as)); \ - MEMORY_LISTENER_CALL(as, callback, dir, &mrs); \ - } while(0) +#define MEMORY_LISTENER_UPDATE_REGION(fr, as, dir, callback) \ + do { \ + MemoryRegionSection mrs = \ + section_from_flat_range(fr, address_space_to_flatview(as)); \ + MEMORY_LISTENER_CALL(as, callback, dir, &mrs); \ + } while (0) -/* Range of memory in the global map. Addresses are absolute. */ -struct FlatRange { - MemoryRegion *mr; - hwaddr offset_in_region; - AddrRange addr; - bool readonly; -}; +static void flatviews_prepare_reclaim(struct uc_struct *uc) +{ + AddressSpace *as; -#define FOR_EACH_FLAT_RANGE(var, view) \ - for (var = (view)->ranges; var < (view)->ranges + (view)->nr; ++var) + if (!uc->flat_views) { + return; + } -static inline MemoryRegionSection -section_from_flat_range(FlatRange *fr, FlatView *fv) + QTAILQ_FOREACH(as, &uc->address_spaces, address_spaces_link) + { + if (as->current_map) { + flatview_ref(as->current_map); + } + } + g_hash_table_destroy(uc->flat_views); + uc->flat_views = + g_hash_table_new_full(NULL, NULL, NULL, flatview_unref_notify); + QTAILQ_FOREACH(as, &uc->address_spaces, address_spaces_link) + { + FlatView *view = as->current_map; + + if (view && !g_hash_table_lookup(uc->flat_views, view->root)) { + g_hash_table_replace(uc->flat_views, view->root, view); + } else if (view) { + flatview_unref(view); + } + } +} + +void memory_mapping_prune(UcMapping *mapping) { - return (MemoryRegionSection) { + MemoryRegion **link = &mapping->regions; + MemoryRegion *root = mapping->root; + + flatviews_prepare_reclaim(mapping->owner); + if (!root || root->terminates) { + return; + } + + while (*link) { + MemoryRegion *region = *link; + MemoryRegion *next = region->mapping_next; + bool active = mapping->active && region->container == root; + + if (region->context_refs == 0 && !active) { + if (region->container) { + assert(region->container == root); + memory_region_remove_subregion(root, region); + } + *link = next; + region->mapping_next = NULL; + region->destructor(region); + g_free(region); + } else { + link = ®ion->mapping_next; + } + } +} + +void memory_mapping_free(UcMapping *mapping) +{ + MemoryRegion *region = mapping->regions; + MemoryRegion *root = mapping->root; + + assert(mapping->context_refs == 0); + assert(!root || !root->container); + if (root && !root->terminates) { + MemoryRegion *child, *next; + + QTAILQ_FOREACH_SAFE(child, &root->subregions, subregions_link, next) + { + memory_region_remove_subregion(root, child); + } + root->destructor(root); + g_free(root); + } + + while (region) { + MemoryRegion *next = region->mapping_next; + + assert(region->context_refs == 0); + assert(!region->container); + region->destructor(region); + g_free(region); + region = next; + } + mapping->root = NULL; + mapping->regions = NULL; +} + +static inline MemoryRegionSection section_from_flat_range(FlatRange *fr, + FlatView *fv) +{ + return (MemoryRegionSection){ .mr = fr->mr, .fv = fv, .offset_within_region = fr->offset_in_region, @@ -466,10 +739,9 @@ section_from_flat_range(FlatRange *fr, FlatView *fv) static bool flatrange_equal(FlatRange *a, FlatRange *b) { - return a->mr == b->mr - && addrrange_equal(a->addr, b->addr) - && a->offset_in_region == b->offset_in_region - && a->readonly == b->readonly; + return a->mr == b->mr && addrrange_equal(a->addr, b->addr) && + a->offset_in_region == b->offset_in_region && + a->readonly == b->readonly; } static FlatView *flatview_new(MemoryRegion *mr_root) @@ -490,8 +762,8 @@ static void flatview_insert(FlatView *view, unsigned pos, FlatRange *range) { if (view->nr == view->nr_allocated) { view->nr_allocated = MAX(2 * view->nr, 10); - view->ranges = g_realloc(view->ranges, - view->nr_allocated * sizeof(*view->ranges)); + view->ranges = + g_realloc(view->ranges, view->nr_allocated * sizeof(*view->ranges)); } memmove(view->ranges + pos + 1, view->ranges + pos, (view->nr - pos) * sizeof(FlatRange)); @@ -521,14 +793,19 @@ void flatview_unref(FlatView *view) } } +static void flatview_unref_notify(gpointer data) +{ + flatview_unref(data); +} + static bool can_merge(FlatRange *r1, FlatRange *r2) { - return int128_eq(addrrange_end(r1->addr), r2->addr.start) - && r1->mr == r2->mr - && int128_eq(int128_add(int128_make64(r1->offset_in_region), - r1->addr.size), - int128_make64(r2->offset_in_region)) - && r1->readonly == r2->readonly; + return int128_eq(addrrange_end(r1->addr), r2->addr.start) && + r1->mr == r2->mr && + int128_eq( + int128_add(int128_make64(r1->offset_in_region), r1->addr.size), + int128_make64(r2->offset_in_region)) && + r1->readonly == r2->readonly; } /* Attempt to simplify a view by merging adjacent ranges */ @@ -539,8 +816,8 @@ static void flatview_simplify(FlatView *view) i = 0; while (i < view->nr) { j = i + 1; - while (j < view->nr - && can_merge(&view->ranges[j-1], &view->ranges[j])) { + while (j < view->nr && + can_merge(&view->ranges[j - 1], &view->ranges[j])) { int128_addto(&view->ranges[i].addr.size, view->ranges[j].addr.size); ++j; } @@ -582,8 +859,7 @@ static void adjust_endianness(MemoryRegion *mr, uint64_t *data, MemOp op) } static inline void memory_region_shift_read_access(uint64_t *value, - signed shift, - uint64_t mask, + signed shift, uint64_t mask, uint64_t tmp) { if (shift >= 0) { @@ -593,9 +869,8 @@ static inline void memory_region_shift_read_access(uint64_t *value, } } -static inline uint64_t memory_region_shift_write_access(uint64_t *value, - signed shift, - uint64_t mask) +static inline uint64_t +memory_region_shift_write_access(uint64_t *value, signed shift, uint64_t mask) { uint64_t tmp; @@ -608,13 +883,11 @@ static inline uint64_t memory_region_shift_write_access(uint64_t *value, return tmp; } -static MemTxResult memory_region_read_accessor(struct uc_struct *uc, MemoryRegion *mr, - hwaddr addr, - uint64_t *value, - unsigned size, - signed shift, - uint64_t mask, - MemTxAttrs attrs) +static MemTxResult memory_region_read_accessor(struct uc_struct *uc, + MemoryRegion *mr, hwaddr addr, + uint64_t *value, unsigned size, + signed shift, uint64_t mask, + MemTxAttrs attrs) { uint64_t tmp; @@ -623,13 +896,9 @@ static MemTxResult memory_region_read_accessor(struct uc_struct *uc, MemoryRegi return MEMTX_OK; } -static MemTxResult memory_region_read_with_attrs_accessor(struct uc_struct *uc, MemoryRegion *mr, - hwaddr addr, - uint64_t *value, - unsigned size, - signed shift, - uint64_t mask, - MemTxAttrs attrs) +static MemTxResult memory_region_read_with_attrs_accessor( + struct uc_struct *uc, MemoryRegion *mr, hwaddr addr, uint64_t *value, + unsigned size, signed shift, uint64_t mask, MemTxAttrs attrs) { uint64_t tmp = 0; MemTxResult r; @@ -639,12 +908,10 @@ static MemTxResult memory_region_read_with_attrs_accessor(struct uc_struct *uc, return r; } -static MemTxResult memory_region_write_accessor(struct uc_struct *uc, MemoryRegion *mr, - hwaddr addr, - uint64_t *value, - unsigned size, - signed shift, - uint64_t mask, +static MemTxResult memory_region_write_accessor(struct uc_struct *uc, + MemoryRegion *mr, hwaddr addr, + uint64_t *value, unsigned size, + signed shift, uint64_t mask, MemTxAttrs attrs) { uint64_t tmp = memory_region_shift_write_access(value, shift, mask); @@ -653,35 +920,22 @@ static MemTxResult memory_region_write_accessor(struct uc_struct *uc, MemoryRegi return MEMTX_OK; } -static MemTxResult memory_region_write_with_attrs_accessor(struct uc_struct *uc, MemoryRegion *mr, - hwaddr addr, - uint64_t *value, - unsigned size, - signed shift, - uint64_t mask, - MemTxAttrs attrs) +static MemTxResult memory_region_write_with_attrs_accessor( + struct uc_struct *uc, MemoryRegion *mr, hwaddr addr, uint64_t *value, + unsigned size, signed shift, uint64_t mask, MemTxAttrs attrs) { uint64_t tmp = memory_region_shift_write_access(value, shift, mask); return mr->ops->write_with_attrs(uc, mr->opaque, addr, tmp, size, attrs); } -static MemTxResult access_with_adjusted_size(struct uc_struct *uc, hwaddr addr, - uint64_t *value, - unsigned size, - unsigned access_size_min, - unsigned access_size_max, - MemTxResult (*access_fn) - (struct uc_struct *uc, - MemoryRegion *mr, - hwaddr addr, - uint64_t *value, - unsigned size, - signed shift, - uint64_t mask, - MemTxAttrs attrs), - MemoryRegion *mr, - MemTxAttrs attrs) +static MemTxResult access_with_adjusted_size( + struct uc_struct *uc, hwaddr addr, uint64_t *value, unsigned size, + unsigned access_size_min, unsigned access_size_max, + MemTxResult (*access_fn)(struct uc_struct *uc, MemoryRegion *mr, + hwaddr addr, uint64_t *value, unsigned size, + signed shift, uint64_t mask, MemTxAttrs attrs), + MemoryRegion *mr, MemTxAttrs attrs) { uint64_t access_mask; unsigned access_size; @@ -701,12 +955,12 @@ static MemTxResult access_with_adjusted_size(struct uc_struct *uc, hwaddr addr, if (memory_region_big_endian(mr)) { for (i = 0; i < size; i += access_size) { r |= access_fn(uc, mr, addr + i, value, access_size, - (size - access_size - i) * 8, access_mask, attrs); + (size - access_size - i) * 8, access_mask, attrs); } } else { for (i = 0; i < size; i += access_size) { r |= access_fn(uc, mr, addr + i, value, access_size, i * 8, - access_mask, attrs); + access_mask, attrs); } } return r; @@ -719,7 +973,8 @@ static AddressSpace *memory_region_to_address_space(MemoryRegion *mr) while (mr->container) { mr = mr->container; } - QTAILQ_FOREACH(as, &mr->uc->address_spaces, address_spaces_link) { + QTAILQ_FOREACH(as, &mr->uc->address_spaces, address_spaces_link) + { if (mr == as->root) { return as; } @@ -730,11 +985,8 @@ static AddressSpace *memory_region_to_address_space(MemoryRegion *mr) /* Render a memory region into the global view. Ranges in @view obscure * ranges in @mr. */ -static void render_memory_region(FlatView *view, - MemoryRegion *mr, - Int128 base, - AddrRange clip, - bool readonly) +static void render_memory_region(FlatView *view, MemoryRegion *mr, Int128 base, + AddrRange clip, bool readonly) { MemoryRegion *subregion; unsigned i; @@ -760,7 +1012,8 @@ static void render_memory_region(FlatView *view, clip = addrrange_intersection(tmp, clip); /* Render subregions in priority order. */ - QTAILQ_FOREACH(subregion, &mr->subregions, subregions_link) { + QTAILQ_FOREACH(subregion, &mr->subregions, subregions_link) + { render_memory_region(view, subregion, base, clip, readonly); } @@ -811,16 +1064,18 @@ static MemoryRegion *memory_region_get_flatview_root(MemoryRegion *mr) if (!mr->terminates) { unsigned int found = 0; MemoryRegion *child, *next = NULL; - QTAILQ_FOREACH(child, &mr->subregions, subregions_link) { + QTAILQ_FOREACH(child, &mr->subregions, subregions_link) + { if (child->enabled) { if (++found > 1) { next = NULL; break; } if (!child->addr && int128_ge(mr->size, child->size)) { - /* A child is included in its entirety. If it's the only - * enabled one, use it in the hope of finding an alias down the - * way. This will also let us share FlatViews. + /* A child is included in its entirety. If it's the + * only enabled one, use it in the hope of finding an + * alias down the way. This will also let us share + * FlatViews. */ next = child; } @@ -842,7 +1097,8 @@ static MemoryRegion *memory_region_get_flatview_root(MemoryRegion *mr) } /* Render a memory topology into a list of disjoint absolute ranges. */ -static FlatView *generate_memory_topology(struct uc_struct *uc, MemoryRegion *mr) +static FlatView *generate_memory_topology(struct uc_struct *uc, + MemoryRegion *mr) { int i; FlatView *view; @@ -901,11 +1157,10 @@ static void address_space_update_topology_pass(AddressSpace *as, frnew = NULL; } - if (frold - && (!frnew - || int128_lt(frold->addr.start, frnew->addr.start) - || (int128_eq(frold->addr.start, frnew->addr.start) - && !flatrange_equal(frold, frnew)))) { + if (frold && + (!frnew || int128_lt(frold->addr.start, frnew->addr.start) || + (int128_eq(frold->addr.start, frnew->addr.start) && + !flatrange_equal(frold, frnew)))) { /* In old but not in new, or in both but attributes changed. */ if (!adding) { @@ -940,8 +1195,8 @@ static void flatviews_init(struct uc_struct *uc) return; } - uc->flat_views = g_hash_table_new_full(NULL, NULL, NULL, - (GDestroyNotify) flatview_unref); + uc->flat_views = + g_hash_table_new_full(NULL, NULL, NULL, flatview_unref_notify); if (!uc->empty_view) { uc->empty_view = generate_memory_topology(uc, NULL); @@ -951,20 +1206,40 @@ static void flatviews_init(struct uc_struct *uc) } } -bool flatview_copy(struct uc_struct *uc, FlatView *dst, FlatView *src, bool update_dispatcher) +bool flatview_reserve(FlatView *view, unsigned int count) { - if (!dst->ranges || !dst->nr_allocated || dst->nr_allocated < src->nr) { - if (dst->ranges && dst->nr_allocated) { - free(dst->ranges); - } - dst->ranges = calloc(src->nr_allocated, sizeof(*dst->ranges)); - if (!dst->ranges) { - return false; - } - dst->nr_allocated = src->nr_allocated; + FlatRange *ranges; + unsigned int nr_allocated; + + if (count <= view->nr_allocated) { + return true; + } + + nr_allocated = MAX(count, MAX(2 * view->nr_allocated, 10)); + ranges = g_try_new(FlatRange, nr_allocated); + if (!ranges) { + return false; + } + if (view->nr != 0) { + memcpy(ranges, view->ranges, view->nr * sizeof(*ranges)); + } + g_free(view->ranges); + view->ranges = ranges; + view->nr_allocated = nr_allocated; + return true; +} + +bool flatview_copy(struct uc_struct *uc, FlatView *dst, FlatView *src, + bool update_dispatcher) +{ + if (!flatview_reserve(dst, src->nr)) { + return false; + } + if (src->nr != 0) { + memcpy(dst->ranges, src->ranges, src->nr * sizeof(*dst->ranges)); } - memcpy(dst->ranges, src->ranges, src->nr*sizeof(*dst->ranges)); dst->nr = src->nr; + dst->root = src->root; if (!update_dispatcher) { return true; } @@ -974,9 +1249,8 @@ bool flatview_copy(struct uc_struct *uc, FlatView *dst, FlatView *src, bool upda } dst->dispatch = address_space_dispatch_new(uc, dst); for (size_t j = 0; j < dst->nr; j++) { - MemoryRegionSection mrs = - section_from_flat_range(&dst->ranges[j], dst); - mrs.mr->subpage = false; + MemoryRegionSection mrs = section_from_flat_range(&dst->ranges[j], dst); + mrs.mr->subpage = false; flatview_add_to_dispatch(uc, dst, &mrs); } address_space_dispatch_compact(dst->dispatch); @@ -984,6 +1258,31 @@ bool flatview_copy(struct uc_struct *uc, FlatView *dst, FlatView *src, bool upda return true; } +void address_space_restore_flatview(AddressSpace *as, FlatView *view) +{ + struct uc_struct *uc = as->uc; + FlatView *old_view = as->current_map; + size_t i; + + assert(view->ref == 1); + assert(!view->dispatch); + MEMORY_LISTENER_CALL_GLOBAL(uc, begin, Forward); + view->dispatch = address_space_dispatch_new(uc, view); + for (i = 0; i < view->nr; i++) { + MemoryRegionSection mrs = + section_from_flat_range(&view->ranges[i], view); + + mrs.mr->subpage = false; + flatview_add_to_dispatch(uc, view, &mrs); + } + address_space_dispatch_compact(view->dispatch); + as->current_map = view; + MEMORY_LISTENER_CALL_GLOBAL(uc, commit, Forward); + if (old_view) { + flatview_unref(old_view); + } +} + static bool flatview_update(FlatView *fv, MemoryRegion *mr) { struct uc_struct *uc = mr->uc; @@ -1033,7 +1332,8 @@ static void flatviews_reset(struct uc_struct *uc) flatviews_init(uc); /* Render unique FVs */ - QTAILQ_FOREACH(as, &uc->address_spaces, address_spaces_link) { + QTAILQ_FOREACH(as, &uc->address_spaces, address_spaces_link) + { MemoryRegion *physmr = memory_region_get_flatview_root(as->root); if (g_hash_table_lookup(uc->flat_views, physmr)) { @@ -1058,7 +1358,7 @@ static void address_space_set_flatview(AddressSpace *as) flatview_ref(new_view); if (!QTAILQ_EMPTY(&as->listeners)) { - FlatView tmpview = { .nr = 0 }, *old_view2 = old_view; + FlatView tmpview = {.nr = 0}, *old_view2 = old_view; if (!old_view2) { old_view2 = &tmpview; @@ -1084,9 +1384,7 @@ static void address_space_update_topology(AddressSpace *as) address_space_set_flatview(as); } -void memory_region_transaction_begin(void) -{ -} +void memory_region_transaction_begin(void) {} static void memory_region_transaction_commit(MemoryRegion *mr) { @@ -1100,7 +1398,8 @@ static void memory_region_transaction_commit(MemoryRegion *mr) if (!fv || !flatview_update(fv, mr)) { flatviews_reset(mr->uc); - QTAILQ_FOREACH(as, &mr->uc->address_spaces, address_spaces_link) { + QTAILQ_FOREACH(as, &mr->uc->address_spaces, address_spaces_link) + { address_space_set_flatview(as); } } @@ -1110,9 +1409,7 @@ static void memory_region_transaction_commit(MemoryRegion *mr) } } -static void memory_region_destructor_none(MemoryRegion *mr) -{ -} +static void memory_region_destructor_none(MemoryRegion *mr) {} static void memory_region_destructor_ram(MemoryRegion *mr) { @@ -1125,9 +1422,7 @@ static void memory_region_destructor_container(MemoryRegion *mr) memory_region_filter_subregions(mr, 0); } -void memory_region_init(struct uc_struct *uc, - MemoryRegion *mr, - uint64_t size) +void memory_region_init(struct uc_struct *uc, MemoryRegion *mr, uint64_t size) { memset(mr, 0, sizeof(*mr)); mr->uc = uc; @@ -1143,8 +1438,7 @@ void memory_region_init(struct uc_struct *uc, } } -static uint64_t unassigned_mem_read(void *opaque, hwaddr addr, - unsigned size) +static uint64_t unassigned_mem_read(void *opaque, hwaddr addr, unsigned size) { #ifdef DEBUG_UNASSIGNED printf("Unassigned mem read " TARGET_FMT_plx "\n", addr); @@ -1152,16 +1446,17 @@ static uint64_t unassigned_mem_read(void *opaque, hwaddr addr, return 0; } -static void unassigned_mem_write(void *opaque, hwaddr addr, - uint64_t val, unsigned size) +static void unassigned_mem_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) { #ifdef DEBUG_UNASSIGNED - printf("Unassigned mem write " TARGET_FMT_plx " = 0x%"PRIx64"\n", addr, val); + printf("Unassigned mem write " TARGET_FMT_plx " = 0x%" PRIx64 "\n", addr, + val); #endif } -static bool unassigned_mem_accepts(struct uc_struct *uc, void *opaque, hwaddr addr, - unsigned size, bool is_write, +static bool unassigned_mem_accepts(struct uc_struct *uc, void *opaque, + hwaddr addr, unsigned size, bool is_write, MemTxAttrs attrs) { return false; @@ -1173,13 +1468,11 @@ const MemoryRegionOps unassigned_mem_ops = { }; bool memory_region_access_valid(struct uc_struct *uc, MemoryRegion *mr, - hwaddr addr, - unsigned size, - bool is_write, + hwaddr addr, unsigned size, bool is_write, MemTxAttrs attrs) { - if (mr->ops->valid.accepts - && !mr->ops->valid.accepts(uc, mr->opaque, addr, size, is_write, attrs)) { + if (mr->ops->valid.accepts && + !mr->ops->valid.accepts(uc, mr->opaque, addr, size, is_write, attrs)) { return false; } @@ -1192,40 +1485,35 @@ bool memory_region_access_valid(struct uc_struct *uc, MemoryRegion *mr, return true; } - if (size > mr->ops->valid.max_access_size - || size < mr->ops->valid.min_access_size) { + if (size > mr->ops->valid.max_access_size || + size < mr->ops->valid.min_access_size) { return false; } return true; } -static MemTxResult memory_region_dispatch_read1(struct uc_struct *uc, MemoryRegion *mr, - hwaddr addr, - uint64_t *pval, - unsigned size, +static MemTxResult memory_region_dispatch_read1(struct uc_struct *uc, + MemoryRegion *mr, hwaddr addr, + uint64_t *pval, unsigned size, MemTxAttrs attrs) { *pval = 0; if (mr->ops->read) { - return access_with_adjusted_size(uc, addr, pval, size, - mr->ops->impl.min_access_size, - mr->ops->impl.max_access_size, - memory_region_read_accessor, - mr, attrs); + return access_with_adjusted_size( + uc, addr, pval, size, mr->ops->impl.min_access_size, + mr->ops->impl.max_access_size, memory_region_read_accessor, mr, + attrs); } else { - return access_with_adjusted_size(uc, addr, pval, size, - mr->ops->impl.min_access_size, - mr->ops->impl.max_access_size, - memory_region_read_with_attrs_accessor, - mr, attrs); + return access_with_adjusted_size( + uc, addr, pval, size, mr->ops->impl.min_access_size, + mr->ops->impl.max_access_size, + memory_region_read_with_attrs_accessor, mr, attrs); } } MemTxResult memory_region_dispatch_read(struct uc_struct *uc, MemoryRegion *mr, - hwaddr addr, - uint64_t *pval, - MemOp op, + hwaddr addr, uint64_t *pval, MemOp op, MemTxAttrs attrs) { unsigned size = memop_size(op); @@ -1242,9 +1530,7 @@ MemTxResult memory_region_dispatch_read(struct uc_struct *uc, MemoryRegion *mr, } MemTxResult memory_region_dispatch_write(struct uc_struct *uc, MemoryRegion *mr, - hwaddr addr, - uint64_t data, - MemOp op, + hwaddr addr, uint64_t data, MemOp op, MemTxAttrs attrs) { unsigned size = memop_size(op); @@ -1257,25 +1543,20 @@ MemTxResult memory_region_dispatch_write(struct uc_struct *uc, MemoryRegion *mr, adjust_endianness(mr, &data, op); if (mr->ops->write) { - return access_with_adjusted_size(uc, addr, &data, size, - mr->ops->impl.min_access_size, - mr->ops->impl.max_access_size, - memory_region_write_accessor, mr, - attrs); + return access_with_adjusted_size( + uc, addr, &data, size, mr->ops->impl.min_access_size, + mr->ops->impl.max_access_size, memory_region_write_accessor, mr, + attrs); } else { - return - access_with_adjusted_size(uc, addr, &data, size, - mr->ops->impl.min_access_size, - mr->ops->impl.max_access_size, - memory_region_write_with_attrs_accessor, - mr, attrs); + return access_with_adjusted_size( + uc, addr, &data, size, mr->ops->impl.min_access_size, + mr->ops->impl.max_access_size, + memory_region_write_with_attrs_accessor, mr, attrs); } } -void memory_region_init_io(struct uc_struct *uc, - MemoryRegion *mr, - const MemoryRegionOps *ops, - void *opaque, +void memory_region_init_io(struct uc_struct *uc, MemoryRegion *mr, + const MemoryRegionOps *ops, void *opaque, uint64_t size) { memory_region_init(uc, mr, size); @@ -1284,10 +1565,8 @@ void memory_region_init_io(struct uc_struct *uc, mr->terminates = true; } -void memory_region_init_ram_ptr(struct uc_struct *uc, - MemoryRegion *mr, - uint64_t size, - void *ptr) +void memory_region_init_ram_ptr(struct uc_struct *uc, MemoryRegion *mr, + uint64_t size, void *ptr) { memory_region_init(uc, mr, size); mr->ram = true; @@ -1326,8 +1605,8 @@ void *memory_region_get_ram_ptr(MemoryRegion *mr) return ptr; } -MemoryRegion *memory_region_from_host(struct uc_struct *uc, - void *ptr, ram_addr_t *offset) +MemoryRegion *memory_region_from_host(struct uc_struct *uc, void *ptr, + ram_addr_t *offset) { RAMBlock *block; @@ -1351,7 +1630,8 @@ static void memory_region_update_container_subregions(MemoryRegion *subregion) memory_region_transaction_begin(); - QTAILQ_FOREACH(other, &mr->subregions, subregions_link) { + QTAILQ_FOREACH(other, &mr->subregions, subregions_link) + { if (subregion->priority >= other->priority) { QTAILQ_INSERT_BEFORE(other, subregion, subregions_link); goto done; @@ -1364,8 +1644,7 @@ static void memory_region_update_container_subregions(MemoryRegion *subregion) memory_region_transaction_commit(subregion); } -static void memory_region_add_subregion_common(MemoryRegion *mr, - hwaddr offset, +static void memory_region_add_subregion_common(MemoryRegion *mr, hwaddr offset, MemoryRegion *subregion) { assert(!subregion->container); @@ -1375,25 +1654,21 @@ static void memory_region_add_subregion_common(MemoryRegion *mr, memory_region_update_container_subregions(subregion); } -void memory_region_add_subregion(MemoryRegion *mr, - hwaddr offset, +void memory_region_add_subregion(MemoryRegion *mr, hwaddr offset, MemoryRegion *subregion) { subregion->priority = 0; memory_region_add_subregion_common(mr, offset, subregion); } -void memory_region_add_subregion_overlap(MemoryRegion *mr, - hwaddr offset, - MemoryRegion *subregion, - int priority) +void memory_region_add_subregion_overlap(MemoryRegion *mr, hwaddr offset, + MemoryRegion *subregion, int priority) { subregion->priority = priority; memory_region_add_subregion_common(mr, offset, subregion); } -void memory_region_del_subregion(MemoryRegion *mr, - MemoryRegion *subregion) +void memory_region_del_subregion(MemoryRegion *mr, MemoryRegion *subregion) { memory_region_transaction_begin(); assert(subregion->container == mr); @@ -1418,17 +1693,17 @@ static int cmp_flatrange_addr(const void *addr_, const void *fr_) static FlatRange *flatview_lookup(FlatView *view, AddrRange addr) { - return bsearch(&addr, view->ranges, view->nr, - sizeof(FlatRange), cmp_flatrange_addr); + return bsearch(&addr, view->ranges, view->nr, sizeof(FlatRange), + cmp_flatrange_addr); } /* Same as memory_region_find, but it does not add a reference to the * returned region. It must be called from an RCU critical section. */ -static MemoryRegionSection memory_region_find_rcu(MemoryRegion *mr, - hwaddr addr, uint64_t size) +static MemoryRegionSection memory_region_find_rcu(MemoryRegion *mr, hwaddr addr, + uint64_t size) { - MemoryRegionSection ret = { .mr = NULL }; + MemoryRegionSection ret = {.mr = NULL}; MemoryRegion *root; AddressSpace *as; AddrRange range; @@ -1436,7 +1711,7 @@ static MemoryRegionSection memory_region_find_rcu(MemoryRegion *mr, FlatRange *fr; addr += mr->addr; - for (root = mr; root->container; ) { + for (root = mr; root->container;) { root = root->container; addr += root->addr; } @@ -1461,16 +1736,16 @@ static MemoryRegionSection memory_region_find_rcu(MemoryRegion *mr, ret.fv = view; range = addrrange_intersection(range, fr->addr); ret.offset_within_region = fr->offset_in_region; - ret.offset_within_region += int128_get64(int128_sub(range.start, - fr->addr.start)); + ret.offset_within_region += + int128_get64(int128_sub(range.start, fr->addr.start)); ret.size = range.size; ret.offset_within_address_space = int128_get64(range.start); ret.readonly = fr->readonly; return ret; } -MemoryRegionSection memory_region_find(MemoryRegion *mr, - hwaddr addr, uint64_t size) +MemoryRegionSection memory_region_find(MemoryRegion *mr, hwaddr addr, + uint64_t size) { MemoryRegionSection ret; @@ -1489,7 +1764,8 @@ static void listener_add_address_space(MemoryListener *listener, } view = address_space_get_flatview(as); - FOR_EACH_FLAT_RANGE(fr, view) { + FOR_EACH_FLAT_RANGE(fr, view) + { MemoryRegionSection section = section_from_flat_range(fr, view); if (listener->region_add) { @@ -1511,7 +1787,8 @@ static void listener_del_address_space(MemoryListener *listener, listener->begin(listener); } view = address_space_get_flatview(as); - FOR_EACH_FLAT_RANGE(fr, view) { + FOR_EACH_FLAT_RANGE(fr, view) + { MemoryRegionSection section = section_from_flat_range(fr, view); if (listener->region_del) { @@ -1539,7 +1816,8 @@ void memory_listener_unregister(MemoryListener *listener) } listener_del_address_space(listener, listener->address_space); - QTAILQ_REMOVE(&listener->address_space->uc->memory_listeners, listener, link); + QTAILQ_REMOVE(&listener->address_space->uc->memory_listeners, listener, + link); QTAILQ_REMOVE(&listener->address_space->listeners, listener, link_as); listener->address_space = NULL; } @@ -1551,8 +1829,7 @@ void address_space_remove_listeners(AddressSpace *as) } } -void address_space_init(struct uc_struct *uc, - AddressSpace *as, +void address_space_init(struct uc_struct *uc, AddressSpace *as, MemoryRegion *root) { as->uc = uc; @@ -1581,10 +1858,8 @@ void address_space_destroy(AddressSpace *as) flatview_unref(as->current_map); } -void memory_region_init_ram(struct uc_struct *uc, - MemoryRegion *mr, - uint64_t size, - uint32_t perms) +void memory_region_init_ram(struct uc_struct *uc, MemoryRegion *mr, + uint64_t size, uint32_t perms) { memory_region_init(uc, mr, size); mr->ram = true; diff --git a/qemu/softmmu/unicorn_vtlb.c b/qemu/softmmu/unicorn_vtlb.c index 491639d5a7..7383b116c1 100644 --- a/qemu/softmmu/unicorn_vtlb.c +++ b/qemu/softmmu/unicorn_vtlb.c @@ -77,6 +77,7 @@ bool unicorn_fill_tlb(CPUState *cs, vaddr address, int size, continue; } handled = true; + uc->tb_exec_frame_publish(uc, retaddr); JIT_CALLBACK_GUARD_VAR(ret, ((uc_cb_tlbevent_t)hook->callback)(uc, address & TARGET_PAGE_MASK, rw_to_mem_type(rw), &e, hook->user_data)); if (ret) { break; @@ -121,7 +122,9 @@ bool unicorn_fill_tlb(CPUState *cs, vaddr address, int size, } if (ret) { - tlb_set_page(cs, address & TARGET_PAGE_MASK, e.paddr & TARGET_PAGE_MASK, perms_to_prot(e.perms), mmu_idx, TARGET_PAGE_SIZE); + tlb_set_page(cs, address & TARGET_PAGE_MASK, + e.paddr & ~((hwaddr)TARGET_PAGE_SIZE - 1), + perms_to_prot(e.perms), mmu_idx, TARGET_PAGE_SIZE); return true; } diff --git a/qemu/sparc.h b/qemu/sparc.h index 43dcf73b69..aa1a0a235d 100644 --- a/qemu/sparc.h +++ b/qemu/sparc.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _sparc #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_sparc #define gen_helper_check_exit_request gen_helper_check_exit_request_sparc +#define gen_helper_exit_request gen_helper_exit_request_sparc +#define helper_check_counted_entry helper_check_counted_entry_sparc +#define helper_check_exit_request helper_check_exit_request_sparc +#define helper_exit_request helper_exit_request_sparc +#define helper_uc_tracecode_single helper_uc_tracecode_single_sparc +#define cpu_restore_icount cpu_restore_icount_sparc +#define cpu_tcg_exit_request cpu_tcg_exit_request_sparc #define unicorn_fill_tlb unicorn_fill_tlb_sparc #define reg_read reg_read_sparc #define reg_write reg_write_sparc @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_sparc #define flatview_translate flatview_translate_sparc #define flatview_copy flatview_copy_sparc +#define flatview_reserve flatview_reserve_sparc +#define address_space_restore_flatview address_space_restore_flatview_sparc #define address_space_translate_for_iotlb address_space_translate_for_iotlb_sparc #define qemu_get_cpu qemu_get_cpu_sparc #define cpu_address_space_init cpu_address_space_init_sparc @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_sparc #define memory_moveout memory_moveout_sparc #define memory_movein memory_movein_sparc +#define memory_mapping_restore_topology memory_mapping_restore_topology_sparc +#define memory_mapping_free memory_mapping_free_sparc +#define memory_mapping_prune memory_mapping_prune_sparc +#define memory_mapping_normalize memory_mapping_normalize_sparc #define memory_free memory_free_sparc #define flatview_unref flatview_unref_sparc #define address_space_get_flatview address_space_get_flatview_sparc @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_sparc #define tb_cleanup tb_cleanup_sparc #define tb_flush tb_flush_sparc +#define tb_flush_jit tb_flush_jit_sparc #define tb_phys_invalidate tb_phys_invalidate_sparc #define tb_gen_code tb_gen_code_sparc #define tb_reset_jump tb_reset_jump_sparc diff --git a/qemu/sparc64.h b/qemu/sparc64.h index b246fca248..e668ea7738 100644 --- a/qemu/sparc64.h +++ b/qemu/sparc64.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _sparc64 #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_sparc64 #define gen_helper_check_exit_request gen_helper_check_exit_request_sparc64 +#define gen_helper_exit_request gen_helper_exit_request_sparc64 +#define helper_check_counted_entry helper_check_counted_entry_sparc64 +#define helper_check_exit_request helper_check_exit_request_sparc64 +#define helper_exit_request helper_exit_request_sparc64 +#define helper_uc_tracecode_single helper_uc_tracecode_single_sparc64 +#define cpu_restore_icount cpu_restore_icount_sparc64 +#define cpu_tcg_exit_request cpu_tcg_exit_request_sparc64 #define unicorn_fill_tlb unicorn_fill_tlb_sparc64 #define reg_read reg_read_sparc64 #define reg_write reg_write_sparc64 @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_sparc64 #define flatview_translate flatview_translate_sparc64 #define flatview_copy flatview_copy_sparc64 +#define flatview_reserve flatview_reserve_sparc64 +#define address_space_restore_flatview address_space_restore_flatview_sparc64 #define address_space_translate_for_iotlb address_space_translate_for_iotlb_sparc64 #define qemu_get_cpu qemu_get_cpu_sparc64 #define cpu_address_space_init cpu_address_space_init_sparc64 @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_sparc64 #define memory_moveout memory_moveout_sparc64 #define memory_movein memory_movein_sparc64 +#define memory_mapping_restore_topology memory_mapping_restore_topology_sparc64 +#define memory_mapping_free memory_mapping_free_sparc64 +#define memory_mapping_prune memory_mapping_prune_sparc64 +#define memory_mapping_normalize memory_mapping_normalize_sparc64 #define memory_free memory_free_sparc64 #define flatview_unref flatview_unref_sparc64 #define address_space_get_flatview address_space_get_flatview_sparc64 @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_sparc64 #define tb_cleanup tb_cleanup_sparc64 #define tb_flush tb_flush_sparc64 +#define tb_flush_jit tb_flush_jit_sparc64 #define tb_phys_invalidate tb_phys_invalidate_sparc64 #define tb_gen_code tb_gen_code_sparc64 #define tb_reset_jump tb_reset_jump_sparc64 diff --git a/qemu/target/arm/helper.c b/qemu/target/arm/helper.c index e73c69d017..7f0e605483 100644 --- a/qemu/target/arm/helper.c +++ b/qemu/target/arm/helper.c @@ -6103,7 +6103,11 @@ void hw_watchpoint_update_all(ARMCPU *cpu) cpu_watchpoint_remove_all(CPU(cpu), BP_CPU); memset(env->cpu_watchpoint, 0, sizeof(env->cpu_watchpoint)); - for (i = 0; i < ARRAY_SIZE(cpu->env.cpu_watchpoint); i++) { + if (!is_a64(env) && !arm_feature(env, ARM_FEATURE_V7)) { + return; + } + + for (i = 0; i < arm_num_wrps(cpu); i++) { hw_watchpoint_update(cpu, i); } } @@ -6221,7 +6225,11 @@ void hw_breakpoint_update_all(ARMCPU *cpu) cpu_breakpoint_remove_all(CPU(cpu), BP_CPU); memset(env->cpu_breakpoint, 0, sizeof(env->cpu_breakpoint)); - for (i = 0; i < ARRAY_SIZE(cpu->env.cpu_breakpoint); i++) { + if (!is_a64(env) && !arm_feature(env, ARM_FEATURE_V7)) { + return; + } + + for (i = 0; i < arm_num_brps(cpu); i++) { hw_breakpoint_update(cpu, i); } } diff --git a/qemu/target/arm/mte_helper.c b/qemu/target/arm/mte_helper.c index bc238752ac..505b481d34 100644 --- a/qemu/target/arm/mte_helper.c +++ b/qemu/target/arm/mte_helper.c @@ -81,8 +81,8 @@ uint64_t HELPER(irg)(CPUARMState *env, uint64_t rn, uint64_t rm) return address_with_allocation_tag(rn, rtag); } -uint64_t HELPER(addsubg)(CPUARMState *env, uint64_t ptr, - int32_t offset, uint32_t tag_offset) +uint64_t HELPER(addsubg)(CPUARMState *env, uint64_t ptr, int32_t offset, + uint32_t tag_offset) { int start_tag = allocation_tag_from_addr(ptr); uint16_t exclude = extract32(env->cp15.gcr_el1, 0, 16); @@ -127,7 +127,7 @@ static bool allocation_tag_cow(CPUARMState *env, RAMBlock *block, end = (addr + size + ~TARGET_PAGE_MASK) & TARGET_PAGE_MASK; while (page < end) { - if (!memory_cow(uc, mr, page, TARGET_PAGE_SIZE)) { + if (!memory_cow(uc, mr->uc_mapping, mr, page, TARGET_PAGE_SIZE)) { uc->invalid_addr = ptr; uc->invalid_error = UC_ERR_NOMEM; cpu_loop_exit_restore(env_cpu(env), ra); @@ -141,20 +141,19 @@ static bool allocation_tag_cow(CPUARMState *env, RAMBlock *block, static void allocation_tag_raise_fault(CPUARMState *env, target_ulong addr, MMUAccessType access_type, - bool mmu_fault, bool prot, - uintptr_t ra) + bool mmu_fault, bool prot, uintptr_t ra) { struct uc_struct *uc = env->uc; uc->invalid_addr = addr; if (access_type == MMU_DATA_STORE) { - uc->invalid_error = mmu_fault ? UC_ERR_MMU_WRITE : - prot ? UC_ERR_WRITE_PROT : - UC_ERR_WRITE_UNMAPPED; + uc->invalid_error = mmu_fault ? UC_ERR_MMU_WRITE + : prot ? UC_ERR_WRITE_PROT + : UC_ERR_WRITE_UNMAPPED; } else { - uc->invalid_error = mmu_fault ? UC_ERR_MMU_READ : - prot ? UC_ERR_READ_PROT : - UC_ERR_READ_UNMAPPED; + uc->invalid_error = mmu_fault ? UC_ERR_MMU_READ + : prot ? UC_ERR_READ_PROT + : UC_ERR_READ_UNMAPPED; } cpu_exit(uc->cpu); cpu_loop_exit_restore(env_cpu(env), ra); @@ -191,8 +190,7 @@ static void *allocation_tag_probe_access(CPUARMState *env, target_ulong ptr, pte_attrs = full->pte_attrs; if (flags & TLB_WATCHPOINT) { - int wp = access_type == MMU_DATA_STORE ? - BP_MEM_WRITE : BP_MEM_READ; + int wp = access_type == MMU_DATA_STORE ? BP_MEM_WRITE : BP_MEM_READ; assert(ra != 0); cpu_check_watchpoint(env_cpu(env), ptr, probe_size, attrs, wp, ra); @@ -214,8 +212,8 @@ static void *allocation_tag_probe_access(CPUARMState *env, target_ulong ptr, } if (access_type == MMU_DATA_STORE) { if (!(mr->perms & UC_PROT_WRITE)) { - allocation_tag_raise_fault(env, paddr, access_type, false, - true, ra); + allocation_tag_raise_fault(env, paddr, access_type, false, true, + ra); } } else if (!(mr->perms & UC_PROT_READ)) { allocation_tag_raise_fault(env, paddr, access_type, false, true, @@ -234,8 +232,7 @@ void HELPER(dc_gva_probe)(CPUARMState *env, uint64_t ptr) uintptr_t ra = GETPC(); int mmu_idx = cpu_mmu_index(env, false); - allocation_tag_probe_access(env, ptr, MMU_DATA_STORE, 1, mmu_idx, ra, - NULL); + allocation_tag_probe_access(env, ptr, MMU_DATA_STORE, 1, mmu_idx, ra, NULL); } void HELPER(mte_probe_data)(CPUARMState *env, uint64_t ptr, uint32_t desc) @@ -245,16 +242,15 @@ void HELPER(mte_probe_data)(CPUARMState *env, uint64_t ptr, uint32_t desc) MMUAccessType access_type; uint32_t size; - access_type = FIELD_EX32(desc, MTEDESC, WRITE) ? MMU_DATA_STORE : - MMU_DATA_LOAD; + access_type = + FIELD_EX32(desc, MTEDESC, WRITE) ? MMU_DATA_STORE : MMU_DATA_LOAD; size = FIELD_EX32(desc, MTEDESC, SIZEM1) + 1; - allocation_tag_probe_access(env, ptr, access_type, size, mmu_idx, ra, - NULL); + allocation_tag_probe_access(env, ptr, access_type, size, mmu_idx, ra, NULL); } -static uint8_t *allocation_tag_mem(CPUARMState *env, int mmu_idx, - uint64_t ptr, MMUAccessType access_type, - int size, bool allocate, uintptr_t ra) +static uint8_t *allocation_tag_mem(CPUARMState *env, int mmu_idx, uint64_t ptr, + MMUAccessType access_type, int size, + bool allocate, uintptr_t ra) { uint64_t clean_ptr = allocation_tag_clean_addr(ptr); void *host; @@ -290,8 +286,7 @@ static uint8_t *allocation_tag_mem(CPUARMState *env, int mmu_idx, if (!allocate) { return NULL; } - block->mte_tags_size = DIV_ROUND_UP(block->max_length, - 2 * TAG_GRANULE); + block->mte_tags_size = DIV_ROUND_UP(block->max_length, 2 * TAG_GRANULE); block->mte_tags = g_malloc0(block->mte_tags_size); } @@ -311,8 +306,8 @@ uint64_t HELPER(ldg)(CPUARMState *env, uint64_t ptr, uint64_t xt) uint8_t *mem; int rtag = 0; - mem = allocation_tag_mem(env, mmu_idx, ptr, MMU_DATA_LOAD, 1, false, - GETPC()); + mem = + allocation_tag_mem(env, mmu_idx, ptr, MMU_DATA_LOAD, 1, false, GETPC()); if (mem) { rtag = load_tag1(ptr, mem); } @@ -414,8 +409,8 @@ uint64_t HELPER(ldgm)(CPUARMState *env, uint64_t ptr) uint8_t *mem; ptr = QEMU_ALIGN_DOWN(ptr, LDGM_STGM_SIZE); - mem = allocation_tag_mem(env, mmu_idx, ptr, MMU_DATA_LOAD, - LDGM_STGM_SIZE, false, ra); + mem = allocation_tag_mem(env, mmu_idx, ptr, MMU_DATA_LOAD, LDGM_STGM_SIZE, + false, ra); if (!mem) { return 0; } @@ -431,8 +426,8 @@ void HELPER(stgm)(CPUARMState *env, uint64_t ptr, uint64_t val) uint8_t *mem; ptr = QEMU_ALIGN_DOWN(ptr, LDGM_STGM_SIZE); - mem = allocation_tag_mem(env, mmu_idx, ptr, MMU_DATA_STORE, - LDGM_STGM_SIZE, true, ra); + mem = allocation_tag_mem(env, mmu_idx, ptr, MMU_DATA_STORE, LDGM_STGM_SIZE, + true, ra); if (!mem) { return; } @@ -453,8 +448,8 @@ void HELPER(stzgm_tags)(CPUARMState *env, uint64_t ptr, uint64_t val) uint8_t *mem; ptr &= -dcz_bytes; - mem = allocation_tag_mem(env, mmu_idx, ptr, MMU_DATA_STORE, dcz_bytes, - true, ra); + mem = allocation_tag_mem(env, mmu_idx, ptr, MMU_DATA_STORE, dcz_bytes, true, + ra); if (mem) { int tag_pair = (val & 0xf) * 0x11; @@ -491,8 +486,8 @@ static void mte_sync_check_fail(CPUARMState *env, uint32_t desc, uint32_t syn; env->exception.vaddress = dirty_ptr; - syn = syn_data_abort_no_iss(arm_current_el(env) != 0, 0, 0, 0, - is_write, 0x11); + syn = syn_data_abort_no_iss(arm_current_el(env) != 0, 0, 0, 0, is_write, + 0x11); raise_exception_ra(env, EXCP_DATA_ABORT, syn, exception_target_el(env), ra); g_assert_not_reached(); } @@ -508,8 +503,8 @@ static void mte_async_check_fail(CPUARMState *env, uint64_t dirty_ptr, env->cp15.tfsr_el[el] |= 1 << select; } -static void mte_check_fail(CPUARMState *env, uint32_t desc, - uint64_t dirty_ptr, uintptr_t ra) +static void mte_check_fail(CPUARMState *env, uint32_t desc, uint64_t dirty_ptr, + uintptr_t ra) { int mmu_idx = FIELD_EX32(desc, MTEDESC, MIDX); ARMMMUIdx arm_mmu_idx = core_to_aa64_mmu_idx(mmu_idx); @@ -623,8 +618,8 @@ static int mte_probe_int(CPUARMState *env, uint32_t desc, uint64_t ptr, next_page = prev_page + TARGET_PAGE_SIZE; if (likely(tag_last - prev_page < TARGET_PAGE_SIZE)) { - mem1 = allocation_tag_mem(env, mmu_idx, ptr, type, sizem1 + 1, - false, ra); + mem1 = + allocation_tag_mem(env, mmu_idx, ptr, type, sizem1 + 1, false, ra); if (!mem1) { return 1; } @@ -661,8 +656,7 @@ static int mte_probe_int(CPUARMState *env, uint32_t desc, uint64_t ptr, return 0; } -uint64_t mte_check(CPUARMState *env, uint32_t desc, uint64_t ptr, - uintptr_t ra) +uint64_t mte_check(CPUARMState *env, uint32_t desc, uint64_t ptr, uintptr_t ra) { uint64_t fault; int ret = mte_probe_int(env, desc, ptr, ra, &fault); @@ -701,8 +695,8 @@ uint64_t HELPER(mte_check_zva)(CPUARMState *env, uint32_t desc, uint64_t ptr) FIELD_DP32(desc, MTEDESC, WRITE, 1, desc); FIELD_DP32(desc, MTEDESC, SIZEM1, dcz_bytes - 1, desc); mmu_idx = FIELD_EX32(desc, MTEDESC, MIDX); - (void)allocation_tag_probe_access(env, ptr, MMU_DATA_STORE, 1, mmu_idx, - ra, NULL); + (void)allocation_tag_probe_access(env, ptr, MMU_DATA_STORE, 1, mmu_idx, ra, + NULL); ret = mte_probe_int(env, desc, align_ptr, ra, &fault); if (unlikely(ret == 0)) { diff --git a/qemu/target/arm/op_helper.c b/qemu/target/arm/op_helper.c index af4e1089c4..8ba73311ab 100644 --- a/qemu/target/arm/op_helper.c +++ b/qemu/target/arm/op_helper.c @@ -293,10 +293,15 @@ void HELPER(wfi)(CPUARMState *env, uint32_t insn_len) HOOK_FOREACH(env->uc, hook, UC_HOOK_INSN) { if (hook->to_delete) continue; + if (hook->insn != + (env->aarch64 ? UC_ARM64_INS_WFI : UC_ARM_INS_WFI)) { + continue; + } if (!HOOK_BOUND_CHECK(hook, env->pc)) continue; if (hook->insn == (env->aarch64 ? UC_ARM64_INS_WFI : UC_ARM_INS_WFI)) { uintptr_t pc = GETPC(); + uc->tb_exec_frame_publish(uc, pc); if (!synced && !uc->skip_sync_pc_on_exit && pc) { cpu_restore_state(uc->cpu, pc, false); synced = true; @@ -305,7 +310,7 @@ void HELPER(wfi)(CPUARMState *env, uint32_t insn_len) } // the last callback may already asked to stop emulation - if (env->uc->stop_request) + if (uc_stop_requested(env->uc)) break; } @@ -995,6 +1000,7 @@ uint32_t HELPER(uc_hooksys64)(CPUARMState *env, uint32_t insn, void *hk) } uint32_t ret; + uc->tb_exec_frame_publish(uc, GETPC()); JIT_CALLBACK_GUARD_VAR(ret, ((uc_cb_insn_sys_t)(hook->callback))(uc, uc_rt, &cp_reg, hook->user_data)); return ret; } diff --git a/qemu/target/arm/sme_helper.c b/qemu/target/arm/sme_helper.c index 7b8d1e658f..c293d3e006 100644 --- a/qemu/target/arm/sme_helper.c +++ b/qemu/target/arm/sme_helper.c @@ -297,7 +297,7 @@ static void sme_ldst1_probe_addr(CPUARMState *env, target_ulong addr, while (size > 0) { target_ulong page_left = -(addr | TARGET_PAGE_MASK); int probe_size = MIN(size, (int)page_left); - target_ulong paddr; + hwaddr paddr; MemoryRegion *mr; if (!tlb_vaddr_to_paddr(env, addr, access_type, mmu_idx, &paddr)) { diff --git a/qemu/target/arm/sve_helper.c b/qemu/target/arm/sve_helper.c index 890c827b2e..75fbb9fdb7 100644 --- a/qemu/target/arm/sve_helper.c +++ b/qemu/target/arm/sve_helper.c @@ -6657,7 +6657,7 @@ static void sve_probe_write_addr(CPUARMState *env, target_ulong addr, while (size > 0) { target_ulong page_left = -(addr | TARGET_PAGE_MASK); int probe_size = MIN(size, (int)page_left); - target_ulong paddr; + hwaddr paddr; MemoryRegion *mr; if (!tlb_vaddr_to_paddr(env, addr, MMU_DATA_STORE, mmu_idx, &paddr)) { @@ -6696,7 +6696,7 @@ static bool sve_can_read_addr(CPUARMState *env, target_ulong addr, while (size > 0) { target_ulong page_left = -(addr | TARGET_PAGE_MASK); int probe_size = MIN(size, (int)page_left); - target_ulong paddr; + hwaddr paddr; MemoryRegion *mr; if (!tlb_vaddr_to_paddr(env, addr, MMU_DATA_LOAD, mmu_idx, &paddr)) { diff --git a/qemu/target/arm/tlb_helper.c b/qemu/target/arm/tlb_helper.c index 76958ebb0e..f9da43bbd0 100644 --- a/qemu/target/arm/tlb_helper.c +++ b/qemu/target/arm/tlb_helper.c @@ -183,7 +183,7 @@ bool arm_cpu_tlb_fill(CPUState *cs, vaddr address, int size, * pass in the exact addresses. */ if (page_size >= TARGET_PAGE_SIZE) { - full.phys_addr &= TARGET_PAGE_MASK; + full.phys_addr &= ~((hwaddr)TARGET_PAGE_SIZE - 1); address &= TARGET_PAGE_MASK; } tlb_set_page_full(cs, mmu_idx, address, &full); diff --git a/qemu/target/arm/translate-a64.c b/qemu/target/arm/translate-a64.c index 593c822c0d..6a0f60b4b7 100644 --- a/qemu/target/arm/translate-a64.c +++ b/qemu/target/arm/translate-a64.c @@ -15275,6 +15275,15 @@ static void disas_a64_insn(CPUARMState *env, DisasContext *s) s->insn = insn; s->base.pc_next += 4; + if (HOOK_EXISTS_BOUNDED(env->uc, UC_HOOK_MEM_FETCH, s->pc_curr)) { + TCGContext *tcg_ctx = env->uc->tcg_ctx; + + gen_a64_set_pc_im(tcg_ctx, s->pc_curr); + gen_uc_tracefetch(tcg_ctx, 4, UC_HOOK_MEM_FETCH_IDX, env->uc, + s->pc_curr); + check_exit_request(tcg_ctx); + } + // Unicorn: trace this instruction on request if (HOOK_EXISTS_BOUNDED(env->uc, UC_HOOK_CODE, s->pc_curr)) { diff --git a/qemu/target/arm/translate.c b/qemu/target/arm/translate.c index 3671b6fb1c..a72f20b9b4 100644 --- a/qemu/target/arm/translate.c +++ b/qemu/target/arm/translate.c @@ -15849,18 +15849,22 @@ static void disas_arm_insn(DisasContext *s, unsigned int insn) return; } - // Unicorn: trace this instruction on request - if (HOOK_EXISTS_BOUNDED(s->uc, UC_HOOK_CODE, s->pc_curr)) { + if (cond == 0xf) { + if (HOOK_EXISTS_BOUNDED(s->uc, UC_HOOK_MEM_FETCH, s->pc_curr)) { + gen_set_pc_im(s, s->pc_curr); + gen_uc_tracefetch(tcg_ctx, 4, UC_HOOK_MEM_FETCH_IDX, s->uc, + s->pc_curr); + check_exit_request(tcg_ctx); + } - // Sync PC in advance - gen_set_pc_im(s, s->pc_curr); - - gen_uc_tracecode(tcg_ctx, 4, UC_HOOK_CODE_IDX, s->uc, s->pc_curr); - // the callback might want to stop emulation immediately - check_exit_request(tcg_ctx); - } + // Unicorn: trace this instruction on request + if (HOOK_EXISTS_BOUNDED(s->uc, UC_HOOK_CODE, s->pc_curr)) { + gen_set_pc_im(s, s->pc_curr); + gen_uc_tracecode(tcg_ctx, 4, UC_HOOK_CODE_IDX, s->uc, + s->pc_curr); + check_exit_request(tcg_ctx); + } - if (cond == 0xf) { /* In ARMv3 and v4 the NV condition is UNPREDICTABLE; we * choose to UNDEF. In ARMv5 and above the space is used * for miscellaneous unconditional instructions. @@ -15921,12 +15925,36 @@ static void disas_arm_insn(DisasContext *s, unsigned int insn) } goto illegal_op; } + + if (cond == 0xe && + HOOK_EXISTS_BOUNDED(s->uc, UC_HOOK_MEM_FETCH, s->pc_curr)) { + gen_set_pc_im(s, s->pc_curr); + gen_uc_tracefetch(tcg_ctx, 4, UC_HOOK_MEM_FETCH_IDX, s->uc, + s->pc_curr); + check_exit_request(tcg_ctx); + } + + // Unicorn: trace this instruction on request + if (HOOK_EXISTS_BOUNDED(s->uc, UC_HOOK_CODE, s->pc_curr)) { + gen_set_pc_im(s, s->pc_curr); + gen_uc_tracecode(tcg_ctx, 4, UC_HOOK_CODE_IDX, s->uc, s->pc_curr); + check_exit_request(tcg_ctx); + } + if (cond != 0xe) { /* if not always execute, we generate a conditional jump to next instruction */ arm_skip_unless(s, cond); } + if (cond != 0xe && + HOOK_EXISTS_BOUNDED(s->uc, UC_HOOK_MEM_FETCH, s->pc_curr)) { + gen_set_pc_im(s, s->pc_curr); + gen_uc_tracefetch(tcg_ctx, 4, UC_HOOK_MEM_FETCH_IDX, s->uc, + s->pc_curr); + check_exit_request(tcg_ctx); + } + /* TODO: Perhaps merge these into one decodetree output file. */ if (disas_a32(s, insn) || disas_vfp(s, insn)) { @@ -16561,15 +16589,32 @@ static void thumb_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) // Unicorn: trace this instruction on request insn_size = is_16bit ? 2 : 4; - if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_CODE, dc->base.pc_next - insn_size)) { + if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_MEM_FETCH, + dc->base.pc_next - insn_size)) { + int type = UC_HOOK_MEM_FETCH_IDX; + + gen_set_pc_im(dc, dc->base.pc_next - insn_size); + if (uc->no_exit_request) { + type |= UC_HOOK_FLAG_NO_STOP; + } + gen_uc_tracefetch(tcg_ctx, insn_size, type, uc, + dc->base.pc_next - insn_size); + check_exit_request(tcg_ctx); + } + + if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_CODE, + dc->base.pc_next - insn_size)) { // Sync PC in advance gen_set_pc_im(dc, dc->base.pc_next - insn_size); if (uc->no_exit_request) { - gen_uc_tracecode(tcg_ctx, insn_size, UC_HOOK_CODE_IDX | UC_HOOK_FLAG_NO_STOP, uc, dc->base.pc_next - insn_size); + gen_uc_tracecode(tcg_ctx, insn_size, + UC_HOOK_CODE_IDX | UC_HOOK_FLAG_NO_STOP, uc, + dc->base.pc_next - insn_size); } else { - gen_uc_tracecode(tcg_ctx, insn_size, UC_HOOK_CODE_IDX, uc, dc->base.pc_next - insn_size); + gen_uc_tracecode(tcg_ctx, insn_size, UC_HOOK_CODE_IDX, uc, + dc->base.pc_next - insn_size); } // the callback might want to stop emulation immediately check_exit_request(tcg_ctx); diff --git a/qemu/target/arm/unicorn_aarch64.c b/qemu/target/arm/unicorn_aarch64.c index 4e94858f53..f7d6451dc4 100644 --- a/qemu/target/arm/unicorn_aarch64.c +++ b/qemu/target/arm/unicorn_aarch64.c @@ -6,6 +6,7 @@ #include "unicorn/unicorn.h" #include "sysemu/cpus.h" #include "cpu.h" +#include "internals.h" #include "kvm-consts.h" #include "unicorn_common.h" #include "uc_priv.h" @@ -450,6 +451,20 @@ static bool arm64_insn_hook_validate(uint32_t insn_enum) return true; } +static uc_err arm64_context_restore(struct uc_struct *uc, + uc_context *context) +{ + ARMCPU *cpu = ARM_CPU(uc->cpu); + CPUARMState *env = &cpu->env; + size_t pointer_offset = offsetof(CPUARMState, cpu_breakpoint); + + memcpy(env, context->data, pointer_offset); + hw_breakpoint_update_all(cpu); + hw_watchpoint_update_all(cpu); + arm_rebuild_hflags(env); + return UC_ERR_OK; +} + uint64_t _uc_pauth_sign(CPUARMState *env, uint64_t ptr, uint64_t diversifier, uint32_t sctlr_bit, ARMPACKey *key, bool data); uint64_t _uc_pauth_sign_ga(CPUARMState *env, uint64_t ptr, uint64_t diversifier); uint64_t _uc_pauth_strip(CPUARMState *env, uint64_t ptr, bool data); @@ -534,6 +549,7 @@ void uc_init(struct uc_struct *uc) uc->cpus_init = arm64_cpus_init; uc->insn_hook_validate = arm64_insn_hook_validate; uc->cpu_context_size = offsetof(CPUARMState, cpu_watchpoint); + uc->context_restore = arm64_context_restore; uc->pauth_sign = arm64_pauth_sign; uc->pauth_strip = arm64_pauth_strip; uc->pauth_auth = arm64_pauth_auth; diff --git a/qemu/target/arm/unicorn_arm.c b/qemu/target/arm/unicorn_arm.c index 64dc9b3784..33e5d81336 100644 --- a/qemu/target/arm/unicorn_arm.c +++ b/qemu/target/arm/unicorn_arm.c @@ -7,6 +7,7 @@ #include "sysemu/cpus.h" #include "sysemu/tcg.h" #include "cpu.h" +#include "internals.h" #include "uc_priv.h" #include "unicorn_common.h" #include "unicorn.h" @@ -39,7 +40,6 @@ static void arm_release(void *ctx) CPUTLBDescFast *fast; ARMELChangeHook *entry, *next; CPUARMState *env = &cpu->env; - uint32_t nr; release_common(ctx); for (i = 0; i < NB_MMU_MODES; i++) { @@ -60,31 +60,15 @@ static void arm_release(void *ctx) g_free(entry); } - if (arm_feature(env, ARM_FEATURE_PMSA) && - arm_feature(env, ARM_FEATURE_V7)) { - nr = cpu->pmsav7_dregion; - if (nr) { - if (arm_feature(env, ARM_FEATURE_V8)) { - g_free(env->pmsav8.rbar[M_REG_NS]); - g_free(env->pmsav8.rlar[M_REG_NS]); - if (arm_feature(env, ARM_FEATURE_M_SECURITY)) { - g_free(env->pmsav8.rbar[M_REG_S]); - g_free(env->pmsav8.rlar[M_REG_S]); - } - } else { - g_free(env->pmsav7.drbar); - g_free(env->pmsav7.drsr); - g_free(env->pmsav7.dracr); - } - } - } - if (arm_feature(env, ARM_FEATURE_M_SECURITY)) { - nr = cpu->sau_sregion; - if (nr) { - g_free(env->sau.rbar); - g_free(env->sau.rlar); - } - } + g_free(env->pmsav7.drbar); + g_free(env->pmsav7.drsr); + g_free(env->pmsav7.dracr); + g_free(env->pmsav8.rbar[M_REG_NS]); + g_free(env->pmsav8.rbar[M_REG_S]); + g_free(env->pmsav8.rlar[M_REG_NS]); + g_free(env->pmsav8.rlar[M_REG_S]); + g_free(env->sau.rbar); + g_free(env->sau.rlar); g_free(cpu->cpreg_indexes); g_free(cpu->cpreg_values); @@ -96,12 +80,12 @@ static void arm_release(void *ctx) static void reg_reset(struct uc_struct *uc) { CPUArchState *env; - (void)uc; env = uc->cpu->env_ptr; memset(env->regs, 0, sizeof(env->regs)); env->pc = 0; + env->thumb = uc->thumb; } /* these functions are implemented in helper.c. */ @@ -731,6 +715,81 @@ static size_t uc_arm_context_size(struct uc_struct *uc) return ret; } +typedef enum ArmContextArrayIndex { + ARM_CONTEXT_PMSAV7_DRBAR, + ARM_CONTEXT_PMSAV7_DRSR, + ARM_CONTEXT_PMSAV7_DRACR, + ARM_CONTEXT_PMSAV8_RBAR_NS, + ARM_CONTEXT_PMSAV8_RBAR_S, + ARM_CONTEXT_PMSAV8_RLAR_NS, + ARM_CONTEXT_PMSAV8_RLAR_S, + ARM_CONTEXT_SAU_RBAR, + ARM_CONTEXT_SAU_RLAR, + ARM_CONTEXT_ARRAY_COUNT, +} ArmContextArrayIndex; + +typedef struct ArmContextArray { + const uint32_t *data; + uint32_t count; +} ArmContextArray; + +static bool uc_arm_context_parse(const uc_context *context, size_t cpu_size, + ArmContextArray *arrays) +{ + const uint8_t *p = (const uint8_t *)context->data; + size_t remaining = context->context_size; + uint32_t count; + int i; + + if (remaining < cpu_size) { + return false; + } + p += cpu_size; + remaining -= cpu_size; + + for (i = 0; i < ARM_CONTEXT_ARRAY_COUNT; i++) { + size_t array_size; + + if (remaining < sizeof(count)) { + return false; + } + memcpy(&count, p, sizeof(count)); + p += sizeof(count); + remaining -= sizeof(count); + if (count > remaining / sizeof(uint32_t)) { + return false; + } + + array_size = (size_t)count * sizeof(uint32_t); + arrays[i].data = (const uint32_t *)p; + arrays[i].count = count; + p += array_size; + remaining -= array_size; + } + + return true; +} + +static uc_err uc_arm_context_validate(struct uc_struct *uc, + const uc_context *context) +{ + ArmContextArray arrays[ARM_CONTEXT_ARRAY_COUNT]; + + return uc_arm_context_parse(context, uc->cpu_context_size, arrays) + ? UC_ERR_OK + : UC_ERR_ARG; +} + +static void uc_arm_context_restore_array(uint32_t *destination, + uint32_t destination_count, + const ArmContextArray *source) +{ + if (destination && source->count == destination_count) { + memcpy(destination, source->data, + sizeof(uint32_t) * destination_count); + } +} + static uc_err uc_arm_context_save(struct uc_struct *uc, uc_context *context) { char *p = NULL; @@ -771,43 +830,68 @@ static uc_err uc_arm_context_save(struct uc_struct *uc, uc_context *context) static uc_err uc_arm_context_restore(struct uc_struct *uc, uc_context *context) { - char *p = NULL; ARMCPU *cpu = (ARMCPU *)uc->cpu; CPUARMState *env = (CPUARMState *)&cpu->env; - uint32_t nr, ctx_nr; + ArmContextArray arrays[ARM_CONTEXT_ARRAY_COUNT]; + uint32_t *pmsav7_drbar = env->pmsav7.drbar; + uint32_t *pmsav7_drsr = env->pmsav7.drsr; + uint32_t *pmsav7_dracr = env->pmsav7.dracr; + uint32_t *pmsav8_rbar_ns = env->pmsav8.rbar[M_REG_NS]; + uint32_t *pmsav8_rbar_s = env->pmsav8.rbar[M_REG_S]; + uint32_t *pmsav8_rlar_ns = env->pmsav8.rlar[M_REG_NS]; + uint32_t *pmsav8_rlar_s = env->pmsav8.rlar[M_REG_S]; + uint32_t *sau_rbar = env->sau.rbar; + uint32_t *sau_rlar = env->sau.rlar; + uint64_t features = env->features; + uint32_t nr; -#define ARM_ENV_RESTORE(field) \ - ctx_nr = *(uint32_t *)p; \ - if (ctx_nr != 0) { \ - p += sizeof(uint32_t); \ - if (field && ctx_nr == nr) { \ - memcpy(field, p, sizeof(uint32_t) * ctx_nr); \ - } \ - p += sizeof(uint32_t) * ctx_nr; \ - } else { \ - p += sizeof(uint32_t); \ + if (!uc_arm_context_parse(context, uc->cpu_context_size, arrays)) { + return UC_ERR_ARG; } - p = context->data; - memcpy(uc->cpu->env_ptr, p, uc->cpu_context_size); - p += uc->cpu_context_size; + memcpy(uc->cpu->env_ptr, context->data, uc->cpu_context_size); + env->pmsav7.drbar = pmsav7_drbar; + env->pmsav7.drsr = pmsav7_drsr; + env->pmsav7.dracr = pmsav7_dracr; + env->pmsav8.rbar[M_REG_NS] = pmsav8_rbar_ns; + env->pmsav8.rbar[M_REG_S] = pmsav8_rbar_s; + env->pmsav8.rlar[M_REG_NS] = pmsav8_rlar_ns; + env->pmsav8.rlar[M_REG_S] = pmsav8_rlar_s; + env->sau.rbar = sau_rbar; + env->sau.rlar = sau_rlar; + env->features = features; + env->uc = uc; nr = cpu->pmsav7_dregion; - ARM_ENV_RESTORE(env->pmsav7.drbar) - ARM_ENV_RESTORE(env->pmsav7.drsr) - ARM_ENV_RESTORE(env->pmsav7.dracr) - ARM_ENV_RESTORE(env->pmsav8.rbar[M_REG_NS]) - ARM_ENV_RESTORE(env->pmsav8.rbar[M_REG_S]) - ARM_ENV_RESTORE(env->pmsav8.rlar[M_REG_NS]) - ARM_ENV_RESTORE(env->pmsav8.rlar[M_REG_S]) + uc_arm_context_restore_array( + env->pmsav7.drbar, nr, &arrays[ARM_CONTEXT_PMSAV7_DRBAR]); + uc_arm_context_restore_array( + env->pmsav7.drsr, nr, &arrays[ARM_CONTEXT_PMSAV7_DRSR]); + uc_arm_context_restore_array( + env->pmsav7.dracr, nr, &arrays[ARM_CONTEXT_PMSAV7_DRACR]); + uc_arm_context_restore_array( + env->pmsav8.rbar[M_REG_NS], nr, + &arrays[ARM_CONTEXT_PMSAV8_RBAR_NS]); + uc_arm_context_restore_array( + env->pmsav8.rbar[M_REG_S], nr, + &arrays[ARM_CONTEXT_PMSAV8_RBAR_S]); + uc_arm_context_restore_array( + env->pmsav8.rlar[M_REG_NS], nr, + &arrays[ARM_CONTEXT_PMSAV8_RLAR_NS]); + uc_arm_context_restore_array( + env->pmsav8.rlar[M_REG_S], nr, + &arrays[ARM_CONTEXT_PMSAV8_RLAR_S]); nr = cpu->sau_sregion; - ARM_ENV_RESTORE(env->sau.rbar) - ARM_ENV_RESTORE(env->sau.rlar) + uc_arm_context_restore_array(env->sau.rbar, nr, + &arrays[ARM_CONTEXT_SAU_RBAR]); + uc_arm_context_restore_array(env->sau.rlar, nr, + &arrays[ARM_CONTEXT_SAU_RLAR]); + + hw_breakpoint_update_all(cpu); + hw_watchpoint_update_all(cpu); + arm_rebuild_hflags(env); -#undef ARM_ENV_RESTORE - // Overwrite uc to our uc - env->uc = uc; return UC_ERR_OK; } @@ -837,6 +921,7 @@ void uc_init(struct uc_struct *uc) uc->cpu_context_size = offsetof(CPUARMState, cpu_watchpoint); uc->context_size = uc_arm_context_size; uc->context_save = uc_arm_context_save; + uc->context_validate = uc_arm_context_validate; uc->context_restore = uc_arm_context_restore; uc_common_init(uc); } diff --git a/qemu/target/i386/bpt_helper.c b/qemu/target/i386/bpt_helper.c index 1cf39ea468..536d754edd 100644 --- a/qemu/target/i386/bpt_helper.c +++ b/qemu/target/i386/bpt_helper.c @@ -163,6 +163,18 @@ void cpu_x86_update_dr7(CPUX86State *env, uint32_t new_dr7) env->hflags = (env->hflags & ~HF_IOBPT_MASK) | iobpt; } +void cpu_x86_update_debug(CPUX86State *env) +{ + CPUState *cs = env_cpu(env); + target_ulong dr7 = env->dr[7]; + + cpu_breakpoint_remove_all(cs, BP_CPU); + cpu_watchpoint_remove_all(cs, BP_CPU); + memset(env->cpu_breakpoint, 0, sizeof(env->cpu_breakpoint)); + env->dr[7] = DR7_FIXED_1; + cpu_x86_update_dr7(env, dr7); +} + static bool check_hw_breakpoints(CPUX86State *env, bool force_dr6_update) { target_ulong dr6; diff --git a/qemu/target/i386/cpu.h b/qemu/target/i386/cpu.h index cd3f42f374..5da72b42a8 100644 --- a/qemu/target/i386/cpu.h +++ b/qemu/target/i386/cpu.h @@ -2035,6 +2035,7 @@ void cpu_x86_update_cr0(CPUX86State *env, uint32_t new_cr0); void cpu_x86_update_cr3(CPUX86State *env, target_ulong new_cr3); void cpu_x86_update_cr4(CPUX86State *env, uint32_t new_cr4); void cpu_x86_update_dr7(CPUX86State *env, uint32_t new_dr7); +void cpu_x86_update_debug(CPUX86State *env); /* hw/pc.c */ uint64_t cpu_get_tsc(CPUX86State *env); diff --git a/qemu/target/i386/misc_helper.c b/qemu/target/i386/misc_helper.c index bea87522fa..d05ffd8dec 100644 --- a/qemu/target/i386/misc_helper.c +++ b/qemu/target/i386/misc_helper.c @@ -117,6 +117,9 @@ void helper_cpuid(CPUX86State *env) HOOK_FOREACH(env->uc, hook, UC_HOOK_INSN) { if (hook->to_delete) continue; + if (hook->insn != UC_X86_INS_CPUID) { + continue; + } if (!HOOK_BOUND_CHECK(hook, env->eip)) continue; @@ -124,6 +127,7 @@ void helper_cpuid(CPUX86State *env) // true -> skip the cpuid instruction if (hook->insn == UC_X86_INS_CPUID) { uintptr_t pc = GETPC(); + uc->tb_exec_frame_publish(uc, pc); if (!synced && !uc->skip_sync_pc_on_exit && pc) { cpu_restore_state(uc->cpu, pc, false); synced = true; @@ -132,7 +136,7 @@ void helper_cpuid(CPUX86State *env) } // the last callback may already asked to stop emulation - if (env->uc->stop_request) + if (uc_stop_requested(env->uc)) break; } @@ -241,6 +245,9 @@ void helper_rdtsc(CPUX86State *env) HOOK_FOREACH(env->uc, hook, UC_HOOK_INSN) { if (hook->to_delete) continue; + if (hook->insn != UC_X86_INS_RDTSC) { + continue; + } if (!HOOK_BOUND_CHECK(hook, env->eip)) continue; @@ -248,6 +255,7 @@ void helper_rdtsc(CPUX86State *env) // true -> skip the rdtsc instruction if (hook->insn == UC_X86_INS_RDTSC) { uintptr_t pc = GETPC(); + uc->tb_exec_frame_publish(uc, pc); if (!synced && !uc->skip_sync_pc_on_exit && pc) { cpu_restore_state(uc->cpu, pc, false); synced = true; @@ -256,7 +264,7 @@ void helper_rdtsc(CPUX86State *env) } // the last callback may already asked to stop emulation - if (env->uc->stop_request) + if (uc_stop_requested(env->uc)) break; } @@ -285,6 +293,9 @@ void helper_rdtscp(CPUX86State *env) HOOK_FOREACH(env->uc, hook, UC_HOOK_INSN) { if (hook->to_delete) continue; + if (hook->insn != UC_X86_INS_RDTSCP) { + continue; + } if (!HOOK_BOUND_CHECK(hook, env->eip)) continue; @@ -292,6 +303,7 @@ void helper_rdtscp(CPUX86State *env) // true -> skip the rdtscp instruction if (hook->insn == UC_X86_INS_RDTSCP) { uintptr_t pc = GETPC(); + uc->tb_exec_frame_publish(uc, pc); if (!synced && !uc->skip_sync_pc_on_exit && pc) { cpu_restore_state(uc->cpu, pc, false); synced = true; @@ -300,7 +312,7 @@ void helper_rdtscp(CPUX86State *env) } // the last callback may already asked to stop emulation - if (env->uc->stop_request) + if (uc_stop_requested(env->uc)) break; } @@ -341,10 +353,14 @@ void helper_wrmsr(CPUX86State *env) { if (hook->to_delete) continue; + if (hook->insn != UC_X86_INS_WRMSR) { + continue; + } if (!HOOK_BOUND_CHECK(hook, env->eip)) continue; if (hook->insn == UC_X86_INS_WRMSR) { uintptr_t pc = GETPC(); + uc->tb_exec_frame_publish(uc, pc); if (!synced && !uc->skip_sync_pc_on_exit && pc) { cpu_restore_state(uc->cpu, pc, false); synced = true; @@ -353,7 +369,7 @@ void helper_wrmsr(CPUX86State *env) skip_wrmsr, ((uc_cb_insn_cpuid_t)hook->callback)(env->uc, hook->user_data)); } - if (env->uc->stop_request) + if (uc_stop_requested(env->uc)) break; } @@ -578,10 +594,14 @@ void helper_rdmsr(CPUX86State *env) { if (hook->to_delete) continue; + if (hook->insn != UC_X86_INS_RDMSR) { + continue; + } if (!HOOK_BOUND_CHECK(hook, env->eip)) continue; if (hook->insn == UC_X86_INS_RDMSR) { uintptr_t pc = GETPC(); + uc->tb_exec_frame_publish(uc, pc); if (!synced && !uc->skip_sync_pc_on_exit && pc) { cpu_restore_state(uc->cpu, pc, false); synced = true; @@ -590,7 +610,7 @@ void helper_rdmsr(CPUX86State *env) skip_rdmsr, ((uc_cb_insn_cpuid_t)hook->callback)(env->uc, hook->user_data)); } - if (env->uc->stop_request) + if (uc_stop_requested(env->uc)) break; } diff --git a/qemu/target/i386/seg_helper.c b/qemu/target/i386/seg_helper.c index ffcb551326..00658c4242 100644 --- a/qemu/target/i386/seg_helper.c +++ b/qemu/target/i386/seg_helper.c @@ -980,10 +980,14 @@ void helper_syscall(CPUX86State *env, int next_eip_addend) HOOK_FOREACH(env->uc, hook, UC_HOOK_INSN) { if (hook->to_delete) continue; + if (hook->insn != UC_X86_INS_SYSCALL) { + continue; + } if (!HOOK_BOUND_CHECK(hook, env->eip)) continue; if (hook->insn == UC_X86_INS_SYSCALL) { uintptr_t pc = GETPC(); + uc->tb_exec_frame_publish(uc, pc); if (!synced && !uc->skip_sync_pc_on_exit && pc) { cpu_restore_state(uc->cpu, pc, false); synced = true; @@ -992,7 +996,7 @@ void helper_syscall(CPUX86State *env, int next_eip_addend) } // the last callback may already asked to stop emulation - if (env->uc->stop_request) + if (uc_stop_requested(env->uc)) break; } @@ -2364,10 +2368,14 @@ void helper_sysenter(CPUX86State *env, int next_eip_addend) HOOK_FOREACH(env->uc, hook, UC_HOOK_INSN) { if (hook->to_delete) continue; + if (hook->insn != UC_X86_INS_SYSENTER) { + continue; + } if (!HOOK_BOUND_CHECK(hook, env->eip)) continue; if (hook->insn == UC_X86_INS_SYSENTER) { uintptr_t pc = GETPC(); + uc->tb_exec_frame_publish(uc, pc); if (!synced && !uc->skip_sync_pc_on_exit && pc) { cpu_restore_state(uc->cpu, pc, false); synced = true; @@ -2376,7 +2384,7 @@ void helper_sysenter(CPUX86State *env, int next_eip_addend) } // the last callback may already asked to stop emulation - if (env->uc->stop_request) + if (uc_stop_requested(env->uc)) break; } diff --git a/qemu/target/i386/translate.c b/qemu/target/i386/translate.c index a1ba11b1a6..f1737e8654 100644 --- a/qemu/target/i386/translate.c +++ b/qemu/target/i386/translate.c @@ -3243,6 +3243,19 @@ static void gen_sty_env_A0(DisasContext *s, int offset, bool align) #include "emit.c.inc" #include "decode-new.c.inc" +static void patch_x86_trace_size(TCGContext *tcg_ctx, TCGOp *prev_op, + target_ulong size) +{ + TCGOp *size_op; + + if (prev_op) { + size_op = QTAILQ_NEXT(prev_op, link); + } else { + size_op = QTAILQ_FIRST(&tcg_ctx->ops); + } + size_op->args[1] = size; +} + /* convert one instruction. s->base.is_jmp is set if the translation must be stopped. Return the next pc value */ static bool disas_insn(DisasContext *s, CPUState *cpu) @@ -3257,10 +3270,22 @@ static bool disas_insn(DisasContext *s, CPUState *cpu) CCOp orig_cc_op = s->cc_op; target_ulong orig_pc_save = s->pc_save; target_ulong pc_start = s->base.pc_next; - TCGOp *tcg_op, *prev_op = NULL; + TCGOp *prev_op = NULL, *fetch_prev_op = NULL; bool insn_hook = false; + bool fetch_hook = false; s->pc = s->base.pc_next; + if (HOOK_EXISTS_BOUNDED(s->uc, UC_HOOK_MEM_FETCH, pc_start)) { + sync_eflags(s); + gen_update_eip_cur(s); + fetch_prev_op = tcg_last_op(tcg_ctx); + fetch_hook = true; + gen_uc_tracefetch(tcg_ctx, UC_TRACE_SIZE_UNKNOWN, + UC_HOOK_MEM_FETCH_IDX, s->uc, pc_start); + check_exit_request(tcg_ctx); + gen_compute_eflags(s); + } + if (HOOK_EXISTS_BOUNDED(s->uc, UC_HOOK_CODE, pc_start)) { sync_eflags(s); gen_update_eip_cur(s); @@ -3286,6 +3311,9 @@ static bool disas_insn(DisasContext *s, CPUState *cpu) case 0: break; case 1: + if (fetch_hook) { + patch_x86_trace_size(tcg_ctx, fetch_prev_op, s->pc - pc_start); + } gen_exception_gpf(s); return true; case 2: @@ -7091,18 +7119,22 @@ static bool disas_insn(DisasContext *s, CPUState *cpu) goto unknown_op; } if (insn_hook) { - if (prev_op) { - tcg_op = QTAILQ_NEXT(prev_op, link); - } else { - tcg_op = QTAILQ_FIRST(&tcg_ctx->ops); - } - tcg_op->args[1] = s->pc - pc_start; + patch_x86_trace_size(tcg_ctx, prev_op, s->pc - pc_start); + } + if (fetch_hook) { + patch_x86_trace_size(tcg_ctx, fetch_prev_op, s->pc - pc_start); } return true; illegal_op: + if (fetch_hook) { + patch_x86_trace_size(tcg_ctx, fetch_prev_op, s->pc - pc_start); + } gen_illegal_opcode(s); return true; unknown_op: + if (fetch_hook) { + patch_x86_trace_size(tcg_ctx, fetch_prev_op, s->pc - pc_start); + } gen_unknown_opcode(env, s); return true; } diff --git a/qemu/target/i386/unicorn.c b/qemu/target/i386/unicorn.c index cc98d596bb..bdd099876d 100644 --- a/qemu/target/i386/unicorn.c +++ b/qemu/target/i386/unicorn.c @@ -2085,6 +2085,20 @@ static bool x86_insn_hook_validate(uint32_t insn_enum) return true; } +static uc_err x86_context_restore(struct uc_struct *uc, uc_context *context) +{ + CPUX86State *env = uc->cpu->env_ptr; + size_t pointer_offset = offsetof(CPUX86State, cpu_breakpoint); + size_t pointer_end = offsetof(CPUX86State, old_exception); + size_t data_size = uc->cpu_context_size; + + memcpy(env, context->data, pointer_offset); + memcpy((uint8_t *)env + pointer_end, context->data + pointer_end, + data_size - pointer_end); + cpu_x86_update_debug(env); + return UC_ERR_OK; +} + static bool x86_opcode_hook_invalidate(uint32_t op, uint32_t flags) { if (op != UC_TCG_OP_SUB) { @@ -2134,6 +2148,7 @@ void uc_init(struct uc_struct *uc) uc->opcode_hook_invalidate = x86_opcode_hook_invalidate; uc->cpus_init = x86_cpus_init; uc->cpu_context_size = offsetof(CPUX86State, end_reset_fields); + uc->context_restore = x86_context_restore; uc_common_init(uc); } diff --git a/qemu/target/m68k/translate.c b/qemu/target/m68k/translate.c index 833720162d..1e8dcd0705 100644 --- a/qemu/target/m68k/translate.c +++ b/qemu/target/m68k/translate.c @@ -5019,8 +5019,7 @@ DISAS_INSN(wdebug) gen_exception(s, s->base.pc_next, EXCP_PRIVILEGE); return; } - /* TODO: Implement wdebug. */ - cpu_abort(env_cpu(env), "WDEBUG not implemented"); + gen_exception(s, s->base.pc_next, EXCP_ILLEGAL); } DISAS_INSN(trap) @@ -6453,7 +6452,10 @@ static void m68k_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) struct uc_struct *uc = dc->uc; TCGContext *tcg_ctx = uc->tcg_ctx; CPUM68KState *env = cpu->env_ptr; + target_ulong pc_start = dc->pc; + TCGOp *tcg_op, *fetch_prev_op = NULL; uint16_t insn; + bool fetch_hook = false; // Unicorn: end address tells us to stop emulation if (uc_addr_is_exit(uc, dc->pc)) { @@ -6461,6 +6463,15 @@ static void m68k_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) return; } + if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_MEM_FETCH, dc->pc)) { + tcg_gen_movi_i32(tcg_ctx, QREG_PC, dc->pc); + fetch_prev_op = tcg_last_op(tcg_ctx); + fetch_hook = true; + gen_uc_tracefetch(tcg_ctx, 0xf1f1f1f1, UC_HOOK_MEM_FETCH_IDX, uc, + dc->pc); + check_exit_request(tcg_ctx); + } + // Unicorn: trace this instruction on request if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_CODE, dc->pc)) { @@ -6478,6 +6489,15 @@ static void m68k_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) do_writebacks(dc); do_release(dc); + if (fetch_hook) { + if (fetch_prev_op) { + tcg_op = QTAILQ_NEXT(fetch_prev_op, link); + } else { + tcg_op = QTAILQ_FIRST(&tcg_ctx->ops); + } + tcg_op->args[1] = dc->pc - pc_start; + } + dc->base.pc_next = dc->pc; if (dc->base.is_jmp == DISAS_NEXT) { diff --git a/qemu/target/m68k/unicorn.c b/qemu/target/m68k/unicorn.c index c1c94371a7..98c5782626 100644 --- a/qemu/target/m68k/unicorn.c +++ b/qemu/target/m68k/unicorn.c @@ -47,6 +47,7 @@ static void reg_reset(struct uc_struct *uc) memset(env->dregs, 0, sizeof(env->dregs)); env->pc = 0; + cpu_m68k_set_ccr(env, 0); } DEFAULT_VISIBILITY @@ -72,7 +73,6 @@ uc_err reg_read(void *_env, int mode, unsigned int regid, void *value, break; case UC_M68K_REG_SR: CHECK_REG_TYPE(uint32_t); - env->cc_op = CC_OP_FLAGS; *(uint32_t *)value = cpu_m68k_get_sr(env); break; case UC_M68K_REG_CR_SFC: diff --git a/qemu/target/mips/translate.c b/qemu/target/mips/translate.c index a41cb49d61..0aa107bb7a 100644 --- a/qemu/target/mips/translate.c +++ b/qemu/target/mips/translate.c @@ -30741,16 +30741,6 @@ static void decode_opc(CPUMIPSState *env, DisasContext *ctx) return; } - /* Handle blikely not taken case */ - if ((ctx->hflags & MIPS_HFLAG_BMASK_BASE) == MIPS_HFLAG_BL) { - TCGLabel *l1 = gen_new_label(tcg_ctx); - - tcg_gen_brcondi_tl(tcg_ctx, TCG_COND_NE, tcg_ctx->bcond, 0, l1); - tcg_gen_movi_i32(tcg_ctx, tcg_ctx->hflags, ctx->hflags & ~MIPS_HFLAG_BMASK); - gen_goto_tb(ctx, 1, ctx->base.pc_next + 4); - gen_set_label(tcg_ctx, l1); - } - op = MASK_OP_MAJOR(ctx->opcode); rs = (ctx->opcode >> 21) & 0x1f; rt = (ctx->opcode >> 16) & 0x1f; @@ -31582,10 +31572,11 @@ static void mips_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) DisasContext *ctx = container_of(dcbase, DisasContext, base); struct uc_struct *uc = cs->uc; TCGContext *tcg_ctx = uc->tcg_ctx; - TCGOp *tcg_op, *prev_op = NULL, *slot_op = NULL; + TCGOp *tcg_op, *prev_op = NULL, *fetch_prev_op = NULL, *slot_op = NULL; int insn_bytes; int is_slot; bool hook_insn = false; + bool fetch_hook = false; TCGv_i32 dyn_is_slot = NULL; is_slot = ctx->hflags & MIPS_HFLAG_BMASK; @@ -31602,6 +31593,26 @@ static void mips_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) slot_op = tcg_last_op(tcg_ctx); tcg_gen_mov_i32(tcg_ctx, tcg_ctx->delay_slot_flag, dyn_is_slot); + /* Annul a not-taken branch-likely slot before instrumenting it. */ + if ((ctx->hflags & MIPS_HFLAG_BMASK_BASE) == MIPS_HFLAG_BL) { + TCGLabel *l1 = gen_new_label(tcg_ctx); + + tcg_gen_brcondi_tl(tcg_ctx, TCG_COND_NE, tcg_ctx->bcond, 0, l1); + tcg_gen_movi_i32(tcg_ctx, tcg_ctx->hflags, + ctx->hflags & ~MIPS_HFLAG_BMASK); + gen_goto_tb(ctx, 1, ctx->base.pc_next + 4); + gen_set_label(tcg_ctx, l1); + } + + if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_MEM_FETCH, ctx->base.pc_next)) { + gen_save_pc(tcg_ctx, ctx->base.pc_next); + fetch_prev_op = tcg_last_op(tcg_ctx); + fetch_hook = true; + gen_uc_tracefetch(tcg_ctx, 4, UC_HOOK_MEM_FETCH_IDX, uc, + ctx->base.pc_next); + check_exit_request(tcg_ctx); + } + // Unicorn: trace this instruction on request if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_CODE, ctx->base.pc_next)) { @@ -31611,7 +31622,8 @@ static void mips_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) // save the last operand prev_op = tcg_last_op(tcg_ctx); hook_insn = true; - gen_uc_tracecode(tcg_ctx, 4, UC_HOOK_CODE_IDX, uc, ctx->base.pc_next); + gen_uc_tracecode(tcg_ctx, 4, UC_HOOK_CODE_IDX, uc, + ctx->base.pc_next); check_exit_request(tcg_ctx); } @@ -31671,6 +31683,15 @@ static void mips_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) tcg_op->args[1] = insn_bytes; } + if (fetch_hook) { + if (fetch_prev_op) { + tcg_op = QTAILQ_NEXT(fetch_prev_op, link); + } else { + tcg_op = QTAILQ_FIRST(&tcg_ctx->ops); + } + tcg_op->args[1] = insn_bytes; + } + if (slot_op) { slot_op->args[1] = is_slot; } diff --git a/qemu/target/ppc/translate.c b/qemu/target/ppc/translate.c index f1d844585d..9aef926a92 100644 --- a/qemu/target/ppc/translate.c +++ b/qemu/target/ppc/translate.c @@ -8492,8 +8492,37 @@ static void ppc_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) prefixed = is_prefix_insn(ctx, ctx->opcode); if (prefixed) { insn_size = 8; + ctx->prefix_opcode = ctx->opcode; + ctx->base.pc_next = pc + 4; + if ((ctx->base.pc_next & 63) == 0) { + if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_CODE, pc)) { + gen_update_nip(ctx, pc); + gen_uc_tracecode(tcg_ctx, insn_size, UC_HOOK_CODE_IDX, uc, + pc); + check_exit_request(tcg_ctx); + } + ctx->base.pc_next = pc + 8; + gen_exception_err(ctx, POWERPC_EXCP_ALIGN, + POWERPC_EXCP_ALIGN_INSN); + ctx->base.is_jmp = DISAS_NORETURN; + return; + } + ctx->opcode = translator_ldl_swap(tcg_ctx, env, ctx->base.pc_next, + need_byteswap(ctx)); + ctx->base.pc_next = pc + 8; + } else { + ctx->base.pc_next = pc + 4; } +#else + ctx->base.pc_next = pc + 4; #endif + + if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_MEM_FETCH, pc)) { + gen_update_nip(ctx, pc); + gen_uc_tracefetch(tcg_ctx, insn_size, UC_HOOK_MEM_FETCH_IDX, uc, pc); + check_exit_request(tcg_ctx); + } + // Unicorn: trace this instruction on request if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_CODE, pc)) { @@ -8507,18 +8536,6 @@ static void ppc_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) #if defined(TARGET_PPC64) if (prefixed) { - ctx->prefix_opcode = ctx->opcode; - ctx->base.pc_next = pc + 4; - if ((ctx->base.pc_next & 63) == 0) { - ctx->base.pc_next = pc + 8; - gen_exception_err(ctx, POWERPC_EXCP_ALIGN, - POWERPC_EXCP_ALIGN_INSN); - ctx->base.is_jmp = DISAS_NORETURN; - return; - } - ctx->opcode = translator_ldl_swap(tcg_ctx, env, ctx->base.pc_next, - need_byteswap(ctx)); - ctx->base.pc_next = pc + 8; LOG_DISAS("translate prefixed opcode %08x %08x (%s)\n", ctx->prefix_opcode, ctx->opcode, @@ -8540,8 +8557,6 @@ static void ppc_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) opc3(ctx->opcode), opc4(ctx->opcode), ctx->le_mode ? "little" : "big"); - ctx->base.pc_next = pc + 4; - if (gen_mma_insn(ctx)) { goto translated; } diff --git a/qemu/target/ppc/unicorn.c b/qemu/target/ppc/unicorn.c index b444ebfbca..4fe7d81816 100644 --- a/qemu/target/ppc/unicorn.c +++ b/qemu/target/ppc/unicorn.c @@ -4,6 +4,7 @@ #include "qemu/osdep.h" #include "hw/ppc/ppc.h" +#include "qemu/timer.h" #include "sysemu/cpus.h" #include "cpu.h" #include "unicorn_common.h" @@ -118,6 +119,13 @@ static void ppc_release(void *ctx) // g_free(tcg_ctx->tb_ctx.tbs); + if (env->tb_env != NULL) { + timer_free(env->tb_env->decr_timer); + timer_free(env->tb_env->hdecr_timer); + g_free(env->tb_env); + env->tb_env = NULL; + } + if (env->nb_tlb != 0) { switch (env->tlb_type) { case TLB_6XX: diff --git a/qemu/target/riscv/debug.c b/qemu/target/riscv/debug.c index 91a4385fab..e1962e1d1d 100644 --- a/qemu/target/riscv/debug.c +++ b/qemu/target/riscv/debug.c @@ -738,3 +738,27 @@ void riscv_trigger_init(CPURISCVState *env) env->cpu_watchpoint[i] = NULL; } } + +void riscv_trigger_update_all(CPURISCVState *env) +{ + CPUState *cs = env_cpu(env); + int i; + + cpu_breakpoint_remove_all(cs, BP_CPU); + cpu_watchpoint_remove_all(cs, BP_CPU); + memset(env->cpu_breakpoint, 0, sizeof(env->cpu_breakpoint)); + memset(env->cpu_watchpoint, 0, sizeof(env->cpu_watchpoint)); + + for (i = 0; i < RV_MAX_TRIGGERS; i++) { + switch (get_trigger_type(env, i)) { + case TRIGGER_TYPE_AD_MATCH: + type2_breakpoint_insert(env, i); + break; + case TRIGGER_TYPE_AD_MATCH6: + type6_breakpoint_insert(env, i); + break; + default: + break; + } + } +} diff --git a/qemu/target/riscv/debug.h b/qemu/target/riscv/debug.h index a1226b4d29..1fbe2c1d38 100644 --- a/qemu/target/riscv/debug.h +++ b/qemu/target/riscv/debug.h @@ -133,5 +133,6 @@ bool riscv_cpu_debug_check_breakpoint(CPUState *cs); bool riscv_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp); void riscv_trigger_init(CPURISCVState *env); +void riscv_trigger_update_all(CPURISCVState *env); #endif /* RISCV_DEBUG_H */ diff --git a/qemu/target/riscv/translate.c b/qemu/target/riscv/translate.c index 30a69177f9..187920f7e2 100644 --- a/qemu/target/riscv/translate.c +++ b/qemu/target/riscv/translate.c @@ -983,8 +983,9 @@ static void riscv_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) TCGContext *tcg_ctx = uc->tcg_ctx; CPURISCVState *env = cpu->env_ptr; uint16_t opcode16; - TCGOp *tcg_op, *prev_op = NULL; + TCGOp *tcg_op, *prev_op = NULL, *fetch_prev_op = NULL; bool insn_hook = false; + bool fetch_hook = false; // Unicorn: end address tells us to stop emulation if (uc_addr_is_exit(uc, ctx->base.pc_next)) { @@ -993,6 +994,16 @@ static void riscv_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) } else { opcode16 = translator_lduw(tcg_ctx, env, ctx->base.pc_next); + if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_MEM_FETCH, + ctx->base.pc_next)) { + tcg_gen_movi_tl(tcg_ctx, tcg_ctx->cpu_pc, ctx->base.pc_next); + fetch_prev_op = tcg_last_op(tcg_ctx); + fetch_hook = true; + gen_uc_tracefetch(tcg_ctx, 4, UC_HOOK_MEM_FETCH_IDX, uc, + ctx->base.pc_next); + check_exit_request(tcg_ctx); + } + // Unicorn: trace this instruction on request if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_CODE, ctx->base.pc_next)) { @@ -1002,7 +1013,8 @@ static void riscv_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) // save the last operand prev_op = tcg_last_op(tcg_ctx); insn_hook = true; - gen_uc_tracecode(tcg_ctx, 4, UC_HOOK_CODE_IDX, uc, ctx->base.pc_next); + gen_uc_tracecode(tcg_ctx, 4, UC_HOOK_CODE_IDX, uc, + ctx->base.pc_next); // the callback might want to stop emulation immediately check_exit_request(tcg_ctx); } @@ -1026,6 +1038,15 @@ static void riscv_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) tcg_op->args[1] = ctx->pc_succ_insn - ctx->base.pc_next; } + if (fetch_hook) { + if (fetch_prev_op) { + tcg_op = QTAILQ_NEXT(fetch_prev_op, link); + } else { + tcg_op = QTAILQ_FIRST(&tcg_ctx->ops); + } + tcg_op->args[1] = extract16(opcode16, 0, 2) == 3 ? 4 : 2; + } + ctx->base.pc_next = ctx->pc_succ_insn; if (ctx->base.is_jmp == DISAS_NEXT) { diff --git a/qemu/target/riscv/unicorn.c b/qemu/target/riscv/unicorn.c index 2beddffec4..a6787ce2ff 100644 --- a/qemu/target/riscv/unicorn.c +++ b/qemu/target/riscv/unicorn.c @@ -7,6 +7,7 @@ #include "cpu.h" #include "unicorn_common.h" #include "cpu_bits.h" +#include "debug.h" #include #include "unicorn.h" #include @@ -479,6 +480,17 @@ static bool riscv_insn_hook_validate(uint32_t insn_enum) return false; } +static uc_err riscv_context_restore(struct uc_struct *uc, + uc_context *context) +{ + CPURISCVState *env = uc->cpu->env_ptr; + size_t pointer_offset = offsetof(CPURISCVState, cpu_breakpoint); + + memcpy(env, context->data, pointer_offset); + riscv_trigger_update_all(env); + return UC_ERR_OK; +} + static int riscv_cpus_init(struct uc_struct *uc, const char *cpu_model) { @@ -505,5 +517,6 @@ void uc_init(struct uc_struct *uc) uc->insn_hook_validate = riscv_insn_hook_validate; uc->cpus_init = riscv_cpus_init; uc->cpu_context_size = offsetof(CPURISCVState, rdtime_fn); + uc->context_restore = riscv_context_restore; uc_common_init(uc); } diff --git a/qemu/target/s390x/translate.c b/qemu/target/s390x/translate.c index 4d237400ff..dfb55bcc5e 100644 --- a/qemu/target/s390x/translate.c +++ b/qemu/target/s390x/translate.c @@ -6827,11 +6827,20 @@ static DisasJumpType translate_one(CPUS390XState *env, DisasContext *s) /* Update insn_start now that we know the ILEN. */ tcg_set_insn_start_param(s->insn_start, 2, s->ilen); + if (HOOK_EXISTS_BOUNDED(s->uc, UC_HOOK_MEM_FETCH, s->base.pc_next)) { + update_psw_addr(s); + update_cc_op(s); + gen_uc_tracefetch(tcg_ctx, s->ilen, UC_HOOK_MEM_FETCH_IDX, s->uc, + s->base.pc_next); + check_exit_request(tcg_ctx); + } + // Unicorn: trace this instruction on request if (HOOK_EXISTS_BOUNDED(s->uc, UC_HOOK_CODE, s->base.pc_next)) { update_psw_addr(s); update_cc_op(s); - gen_uc_tracecode(tcg_ctx, s->ilen, UC_HOOK_CODE_IDX, s->uc, s->base.pc_next); + gen_uc_tracecode(tcg_ctx, s->ilen, UC_HOOK_CODE_IDX, s->uc, + s->base.pc_next); // the callback might want to stop emulation immediately check_exit_request(tcg_ctx); } diff --git a/qemu/target/sparc/cpu.c b/qemu/target/sparc/cpu.c index a983822c19..19859222c4 100644 --- a/qemu/target/sparc/cpu.c +++ b/qemu/target/sparc/cpu.c @@ -561,6 +561,7 @@ SPARCCPU *cpu_sparc_init(struct uc_struct *uc) cpu_address_space_init(cs, 0, cs->memory); qemu_init_vcpu(cs); + cpu_reset(cs); return cpu; } diff --git a/qemu/target/sparc/translate.c b/qemu/target/sparc/translate.c index 95b24ab649..b99779c2a6 100644 --- a/qemu/target/sparc/translate.c +++ b/qemu/target/sparc/translate.c @@ -3234,6 +3234,9 @@ static void disas_sparc_insn(DisasContext * dc, unsigned int insn) (GET_FIELD_SP(insn, 20, 21) << 14); target = sign_extend(target, 16); target <<= 2; + if ((GET_FIELD_SP(insn, 25, 27) & 3) == 0) { + goto illegal_insn; + } cpu_src1 = get_src1(dc, insn); do_branch_reg(dc, target, insn, cpu_src1); goto jmp_insn; @@ -5959,6 +5962,13 @@ static void sparc_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) return; } + if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_MEM_FETCH, dc->pc)) { + tcg_gen_movi_tl(tcg_ctx, tcg_ctx->cpu_pc, dc->pc); + tcg_gen_movi_tl(tcg_ctx, tcg_ctx->cpu_npc, dc->npc); + gen_uc_tracefetch(tcg_ctx, 4, UC_HOOK_MEM_FETCH_IDX, uc, dc->pc); + check_exit_request(tcg_ctx); + } + // Unicorn: trace this instruction on request if (HOOK_EXISTS_BOUNDED(uc, UC_HOOK_CODE, dc->pc)) { diff --git a/qemu/target/sparc/unicorn64.c b/qemu/target/sparc/unicorn64.c index cfb609d1b1..d24149181f 100644 --- a/qemu/target/sparc/unicorn64.c +++ b/qemu/target/sparc/unicorn64.c @@ -101,7 +101,21 @@ static void sparc_set_ccr(CPUSPARCState *env, target_ulong value) static void sparc_release(void *ctx) { + int i; + TCGContext *tcg_ctx = (TCGContext *)ctx; + SPARCCPU *cpu = (SPARCCPU *)tcg_ctx->uc->cpu; + CPUTLBDesc *d = cpu->neg.tlb.d; + CPUTLBDescFast *f = cpu->neg.tlb.f; + CPUTLBDesc *desc; + CPUTLBDescFast *fast; + release_common(ctx); + for (i = 0; i < NB_MMU_MODES; i++) { + desc = &(d[i]); + fast = &(f[i]); + g_free(desc->fulltlb); + g_free(fast->table); + } #if 0 int i; diff --git a/qemu/target/tricore/translate.c b/qemu/target/tricore/translate.c index 75188b8be6..e95f4ed8dc 100644 --- a/qemu/target/tricore/translate.c +++ b/qemu/target/tricore/translate.c @@ -9238,8 +9238,17 @@ static void tricore_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) is_16bit = tricore_insn_is_16bit(insn_lo); insn_size = is_16bit ? 2 : 4; + if (HOOK_EXISTS_BOUNDED(ctx->uc, UC_HOOK_MEM_FETCH, + ctx->base.pc_next)) { + gen_save_pc(ctx, ctx->base.pc_next); + gen_uc_tracefetch(tcg_ctx, insn_size, UC_HOOK_MEM_FETCH_IDX, + ctx->uc, ctx->base.pc_next); + check_exit_request(tcg_ctx); + } + // Unicorn: trace this instruction on request - if (HOOK_EXISTS_BOUNDED(ctx->uc, UC_HOOK_CODE, ctx->base.pc_next)) { + if (HOOK_EXISTS_BOUNDED(ctx->uc, UC_HOOK_CODE, + ctx->base.pc_next)) { // Sync PC in advance gen_save_pc(ctx, ctx->base.pc_next); diff --git a/qemu/tcg/aarch64/tcg-target.inc.c b/qemu/tcg/aarch64/tcg-target.inc.c index 28a5ce2da0..a46357d121 100644 --- a/qemu/tcg/aarch64/tcg-target.inc.c +++ b/qemu/tcg/aarch64/tcg-target.inc.c @@ -1643,7 +1643,7 @@ static bool tcg_out_qemu_ld_slow_path(TCGContext *s, TCGLabelQemuLdst *lb) MemOp opc = get_memop(oi); MemOp size = opc & MO_SIZE; - const int type = tcg_uc_has_hookmem(s) ? R_AARCH64_JUMP26 : R_AARCH64_CONDBR19; + const int type = R_AARCH64_CONDBR19; if (!patch_reloc(lb->label_ptr[0], type, (intptr_t)tcg_splitwx_to_rx(s->code_ptr), 0)) { return false; @@ -1670,7 +1670,7 @@ static bool tcg_out_qemu_st_slow_path(TCGContext *s, TCGLabelQemuLdst *lb) MemOp opc = get_memop(oi); MemOp size = opc & MO_SIZE; - const int type = tcg_uc_has_hookmem(s) ? R_AARCH64_JUMP26 : R_AARCH64_CONDBR19; + const int type = R_AARCH64_CONDBR19; if (!patch_reloc(lb->label_ptr[0], type, (intptr_t)tcg_splitwx_to_rx(s->code_ptr), 0)) { return false; @@ -1771,11 +1771,7 @@ static void tcg_out_tlb_read(TCGContext *s, TCGReg addr_reg, MemOp opc, /* If not equal, we jump to the slow path. */ *label_ptr = s->code_ptr; - // Unicorn: fast path if hookmem is not enabled - if (!tcg_uc_has_hookmem(s)) - tcg_out_insn(s, 3202, B_C, TCG_COND_NE, 0); - else - tcg_out_insn(s, 3206, B, 0); + tcg_out_insn(s, 3202, B_C, TCG_COND_NE, 0); } #endif /* CONFIG_SOFTMMU */ diff --git a/qemu/tcg/arm/tcg-target.inc.c b/qemu/tcg/arm/tcg-target.inc.c index 22f4e47220..24f68558ae 100644 --- a/qemu/tcg/arm/tcg-target.inc.c +++ b/qemu/tcg/arm/tcg-target.inc.c @@ -1601,11 +1601,7 @@ static void tcg_out_qemu_ld(TCGContext *s, const TCGArg *args, bool is64) /* This a conditional BL only to load a pointer within this opcode into LR for the slow path. We will not be using the value for a tail call. */ label_ptr = s->code_ptr; - if (!tcg_uc_has_hookmem(s)) { - tcg_out_bl(s, COND_NE, 0); - } else { - tcg_out_bl(s, COND_AL, 0); - } + tcg_out_bl(s, COND_NE, 0); tcg_out_qemu_ld_index(s, opc, datalo, datahi, addrlo, addend); @@ -1737,11 +1733,7 @@ static void tcg_out_qemu_st(TCGContext *s, const TCGArg *args, bool is64) /* The conditional call must come last, as we're going to return here. */ label_ptr = s->code_ptr; - if (!tcg_uc_has_hookmem(s)) { - tcg_out_bl(s, COND_NE, 0); - } else { - tcg_out_bl(s, COND_AL, 0); - } + tcg_out_bl(s, COND_NE, 0); add_qemu_ldst_label(s, false, oi, datalo, datahi, addrlo, addrhi, s->code_ptr, label_ptr); diff --git a/qemu/tcg/i386/tcg-target.inc.c b/qemu/tcg/i386/tcg-target.inc.c index b4fd036d4c..73b26087b1 100644 --- a/qemu/tcg/i386/tcg-target.inc.c +++ b/qemu/tcg/i386/tcg-target.inc.c @@ -1762,12 +1762,8 @@ static inline void tcg_out_tlb_load(TCGContext *s, TCGReg addrlo, TCGReg addrhi, path function argument setup. */ tcg_out_mov(s, ttype, r1, addrlo); - // Unicorn: fast path if hookmem is not enable - if (!tcg_uc_has_hookmem(s)) - tcg_out_opc(s, OPC_JCC_long + JCC_JNE, 0, 0, 0); - else - /* slow_path, so data access will go via load_helper() */ - tcg_out_opc(s, OPC_JMP_long, 0, 0, 0); + /* jne slow_path */ + tcg_out_opc(s, OPC_JCC_long + JCC_JNE, 0, 0, 0); label_ptr[0] = s->code_ptr; s->code_ptr += 4; diff --git a/qemu/tcg/loongarch64/tcg-target.inc.c b/qemu/tcg/loongarch64/tcg-target.inc.c index 6a4c5ed9a2..0074214832 100644 --- a/qemu/tcg/loongarch64/tcg-target.inc.c +++ b/qemu/tcg/loongarch64/tcg-target.inc.c @@ -1151,8 +1151,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, /* Compare masked address with the TLB entry. */ ldst->label_ptr[0] = s->code_ptr; - // tcg_out_opc_bne(s, TCG_REG_TMP0, TCG_REG_TMP1, 0); - tcg_out_opc_beq(s, 0, 0, 0); + tcg_out_opc_bne(s, TCG_REG_TMP0, TCG_REG_TMP1, 0); h->index = TCG_REG_TMP2; #else diff --git a/qemu/tcg/ppc/tcg-target.inc.c b/qemu/tcg/ppc/tcg-target.inc.c index 41af409df4..4c9e7e9b1b 100644 --- a/qemu/tcg/ppc/tcg-target.inc.c +++ b/qemu/tcg/ppc/tcg-target.inc.c @@ -2018,7 +2018,7 @@ static bool tcg_out_qemu_ld_slow_path(TCGContext *s, TCGLabelQemuLdst *lb) MemOp opc = get_memop(oi); TCGReg hi, lo, arg = TCG_REG_R3; - const int type = tcg_uc_has_hookmem(s) ? R_PPC_REL24 : R_PPC_REL14; + const int type = R_PPC_REL14; if (!patch_reloc(lb->label_ptr[0], type, (intptr_t)s->code_ptr, 0)) { return false; } @@ -2067,7 +2067,7 @@ static bool tcg_out_qemu_st_slow_path(TCGContext *s, TCGLabelQemuLdst *lb) MemOp s_bits = opc & MO_SIZE; TCGReg hi, lo, arg = TCG_REG_R3; - const int type = tcg_uc_has_hookmem(s) ? R_PPC_REL24 : R_PPC_REL14; + const int type = R_PPC_REL14; if (!patch_reloc(lb->label_ptr[0], type, (intptr_t)s->code_ptr, 0)) { return false; } @@ -2148,11 +2148,7 @@ static void tcg_out_qemu_ld(TCGContext *s, const TCGArg *args, bool is_64) /* Load a pointer into the current opcode w/conditional branch-link. */ label_ptr = s->code_ptr; - // Unicorn: fast path if hookmem is not enabled - if (!tcg_uc_has_hookmem(s)) - tcg_out32(s, BC | BI(7, CR_EQ) | BO_COND_FALSE | LK); - else - tcg_out32(s, B | LK); + tcg_out32(s, BC | BI(7, CR_EQ) | BO_COND_FALSE | LK); rbase = TCG_REG_R3; #else /* !CONFIG_SOFTMMU */ @@ -2227,11 +2223,7 @@ static void tcg_out_qemu_st(TCGContext *s, const TCGArg *args, bool is_64) /* Load a pointer into the current opcode w/conditional branch-link. */ label_ptr = s->code_ptr; - // Unicorn: fast path if hookmem is not enabled - if (!tcg_uc_has_hookmem(s)) - tcg_out32(s, BC | BI(7, CR_EQ) | BO_COND_FALSE | LK); - else - tcg_out32(s, B | LK); + tcg_out32(s, BC | BI(7, CR_EQ) | BO_COND_FALSE | LK); rbase = TCG_REG_R3; #else /* !CONFIG_SOFTMMU */ diff --git a/qemu/tcg/riscv/tcg-target.inc.c b/qemu/tcg/riscv/tcg-target.inc.c index d1d7613349..87440ff99b 100644 --- a/qemu/tcg/riscv/tcg-target.inc.c +++ b/qemu/tcg/riscv/tcg-target.inc.c @@ -1013,12 +1013,7 @@ static void tcg_out_tlb_load(TCGContext *s, TCGReg addrl, /* Compare masked address with the TLB entry. */ label_ptr[0] = s->code_ptr; - /* Unicorn: when memory hooks are present, always branch to slow path */ - if (tcg_uc_has_hookmem(s)) { - tcg_out_opc_branch(s, OPC_BEQ, TCG_REG_ZERO, TCG_REG_ZERO, 0); - } else { - tcg_out_opc_branch(s, OPC_BNE, TCG_REG_TMP0, TCG_REG_TMP1, 0); - } + tcg_out_opc_branch(s, OPC_BNE, TCG_REG_TMP0, TCG_REG_TMP1, 0); /* NOP to allow patching later */ tcg_out_opc_imm(s, OPC_ADDI, TCG_REG_ZERO, TCG_REG_ZERO, 0); diff --git a/qemu/tcg/s390/tcg-target.inc.c b/qemu/tcg/s390/tcg-target.inc.c index 85c010f892..9c4c40cd9b 100644 --- a/qemu/tcg/s390/tcg-target.inc.c +++ b/qemu/tcg/s390/tcg-target.inc.c @@ -1705,10 +1705,7 @@ static void tcg_out_qemu_ld(TCGContext* s, TCGReg data_reg, TCGReg addr_reg, base_reg = tcg_out_tlb_read(s, addr_reg, opc, mem_index, 1); - if (!tcg_uc_has_hookmem(s)) - tcg_out16(s, RI_BRC | (S390_CC_NE << 4)); - else - tcg_out16(s, RI_BRC | (S390_CC_ALWAYS << 4)); + tcg_out16(s, RI_BRC | (S390_CC_NE << 4)); label_ptr = s->code_ptr; s->code_ptr += 1; @@ -1735,10 +1732,7 @@ static void tcg_out_qemu_st(TCGContext* s, TCGReg data_reg, TCGReg addr_reg, base_reg = tcg_out_tlb_read(s, addr_reg, opc, mem_index, 0); - if (!tcg_uc_has_hookmem(s)) - tcg_out16(s, RI_BRC | (S390_CC_NE << 4)); - else - tcg_out16(s, RI_BRC | (S390_CC_ALWAYS << 4)); + tcg_out16(s, RI_BRC | (S390_CC_NE << 4)); label_ptr = s->code_ptr; s->code_ptr += 1; diff --git a/qemu/tcg/tcg-op.c b/qemu/tcg/tcg-op.c index d76153d587..e064204828 100644 --- a/qemu/tcg/tcg-op.c +++ b/qemu/tcg/tcg-op.c @@ -2812,10 +2812,21 @@ static inline MemOp tcg_canonicalize_memop(MemOp op, bool is64, bool st) return op; } -static void gen_ldst_i32(TCGContext *tcg_ctx, TCGOpcode opc, TCGv_i32 val, TCGv addr, - MemOp memop, TCGArg idx) +typedef enum TCGMemExitMode { + TCG_MEM_EXIT_NONE, + TCG_MEM_EXIT_GENERATED_HELPER, + TCG_MEM_EXIT_SLOW_EPILOGUE, +} TCGMemExitMode; + +static void gen_ldst_i32(TCGContext *tcg_ctx, TCGOpcode opc, TCGv_i32 val, + TCGv addr, MemOp memop, TCGArg idx, + TCGMemExitMode exit_mode) { TCGMemOpIdx oi = make_memop_idx(memop, idx); + + if (exit_mode == TCG_MEM_EXIT_SLOW_EPILOGUE) { + oi |= TCG_MO_EXIT_REQUEST; + } #if TARGET_LONG_BITS == 32 tcg_gen_op3i_i32(tcg_ctx, opc, val, addr, oi); #else @@ -2827,10 +2838,15 @@ static void gen_ldst_i32(TCGContext *tcg_ctx, TCGOpcode opc, TCGv_i32 val, TCGv #endif } -static void gen_ldst_i64(TCGContext *tcg_ctx, TCGOpcode opc, TCGv_i64 val, TCGv addr, - MemOp memop, TCGArg idx) +static void gen_ldst_i64(TCGContext *tcg_ctx, TCGOpcode opc, TCGv_i64 val, + TCGv addr, MemOp memop, TCGArg idx, + TCGMemExitMode exit_mode) { TCGMemOpIdx oi = make_memop_idx(memop, idx); + + if (exit_mode == TCG_MEM_EXIT_SLOW_EPILOGUE) { + oi |= TCG_MO_EXIT_REQUEST; + } #if TARGET_LONG_BITS == 32 #if TCG_TARGET_REG_BITS == 32 tcg_gen_op4i_i32(tcg_ctx, opc, TCGV_LOW(tcg_ctx, val), TCGV_HIGH(tcg_ctx, val), addr, oi); @@ -2847,6 +2863,42 @@ static void gen_ldst_i64(TCGContext *tcg_ctx, TCGOpcode opc, TCGv_i64 val, TCGv #endif } +static void gen_check_exit_request(TCGContext *tcg_ctx) +{ + TCGv_ptr puc = tcg_const_ptr(tcg_ctx, tcg_ctx->uc); + TCGv_i32 tmp = tcg_const_i32(tcg_ctx, 0); + + if (tcg_ctx->delay_slot_flag != NULL) { + tcg_gen_mov_i32(tcg_ctx, tmp, tcg_ctx->delay_slot_flag); + } + gen_helper_check_exit_request(tcg_ctx, puc, tmp); + tcg_temp_free_i32(tcg_ctx, tmp); + tcg_temp_free_ptr(tcg_ctx, puc); +} + +static TCGMemExitMode select_mem_exit_mode(TCGContext *tcg_ctx) +{ + if (tcg_ctx->skip_next_exit_check) { + tcg_ctx->skip_next_exit_check = false; + return TCG_MEM_EXIT_NONE; + } + if (tcg_ctx->uc->no_exit_request) { + return TCG_MEM_EXIT_NONE; + } + if (tcg_ctx->delay_slot_flag != NULL) { + return TCG_MEM_EXIT_GENERATED_HELPER; + } + return TCG_MEM_EXIT_SLOW_EPILOGUE; +} + +static void gen_mem_exit_check(TCGContext *tcg_ctx, + TCGMemExitMode exit_mode) +{ + if (exit_mode == TCG_MEM_EXIT_GENERATED_HELPER) { + gen_check_exit_request(tcg_ctx); + } +} + // Unicorn engine // check if the last memory access was invalid // if so, we jump to the block epilogue to quit immediately. @@ -2855,21 +2907,15 @@ void check_exit_request(TCGContext *tcg_ctx) // Unicorn: // For ARM IT block, we couldn't exit in the middle of the // block and this is the our hack here. + if (tcg_ctx->skip_next_exit_check) { + tcg_ctx->skip_next_exit_check = false; + return; + } if (tcg_ctx->uc->no_exit_request) { return; } - TCGv_ptr puc = tcg_const_ptr(tcg_ctx, tcg_ctx->uc); - TCGv_i32 tmp = tcg_const_i32(tcg_ctx, 0); - // Unicorn: - // We CANT'T use brcondi_i32 here or we will fail liveness analysis - // because it marks the end of BB - if (tcg_ctx->delay_slot_flag != NULL) { - tcg_gen_mov_i32(tcg_ctx, tmp, tcg_ctx->delay_slot_flag); - } - gen_helper_check_exit_request(tcg_ctx, puc, tmp); - tcg_temp_free_i32(tcg_ctx, tmp); - tcg_temp_free_ptr(tcg_ctx, puc); + gen_check_exit_request(tcg_ctx); } static void tcg_gen_req_mo(TCGContext *tcg_ctx, TCGBar type) @@ -2883,7 +2929,9 @@ static void tcg_gen_req_mo(TCGContext *tcg_ctx, TCGBar type) } } -void tcg_gen_qemu_ld_i32(TCGContext *tcg_ctx, TCGv_i32 val, TCGv addr, TCGArg idx, MemOp memop) +static void tcg_gen_qemu_ld_i32_internal(TCGContext *tcg_ctx, TCGv_i32 val, + TCGv addr, TCGArg idx, MemOp memop, + TCGMemExitMode exit_mode) { MemOp orig_memop; @@ -2899,7 +2947,8 @@ void tcg_gen_qemu_ld_i32(TCGContext *tcg_ctx, TCGv_i32 val, TCGv addr, TCGArg id } } - gen_ldst_i32(tcg_ctx, INDEX_op_qemu_ld_i32, val, addr, memop, idx); + gen_ldst_i32(tcg_ctx, INDEX_op_qemu_ld_i32, val, addr, memop, idx, + exit_mode); if ((orig_memop ^ memop) & MO_BSWAP) { switch (orig_memop & MO_SIZE) { @@ -2917,10 +2966,20 @@ void tcg_gen_qemu_ld_i32(TCGContext *tcg_ctx, TCGv_i32 val, TCGv addr, TCGArg id } } - check_exit_request(tcg_ctx); } -void tcg_gen_qemu_st_i32(TCGContext *tcg_ctx, TCGv_i32 val, TCGv addr, TCGArg idx, MemOp memop) +void tcg_gen_qemu_ld_i32(TCGContext *tcg_ctx, TCGv_i32 val, TCGv addr, + TCGArg idx, MemOp memop) +{ + TCGMemExitMode exit_mode = select_mem_exit_mode(tcg_ctx); + + tcg_gen_qemu_ld_i32_internal(tcg_ctx, val, addr, idx, memop, exit_mode); + gen_mem_exit_check(tcg_ctx, exit_mode); +} + +static void tcg_gen_qemu_st_i32_internal(TCGContext *tcg_ctx, TCGv_i32 val, + TCGv addr, TCGArg idx, MemOp memop, + TCGMemExitMode exit_mode) { TCGv_i32 swap = NULL; @@ -2946,28 +3005,39 @@ void tcg_gen_qemu_st_i32(TCGContext *tcg_ctx, TCGv_i32 val, TCGv addr, TCGArg id } #endif - gen_ldst_i32(tcg_ctx, INDEX_op_qemu_st_i32, val, addr, memop, idx); + gen_ldst_i32(tcg_ctx, INDEX_op_qemu_st_i32, val, addr, memop, idx, + exit_mode); if (swap) { tcg_temp_free_i32(tcg_ctx, swap); } - check_exit_request(tcg_ctx); +} + +void tcg_gen_qemu_st_i32(TCGContext *tcg_ctx, TCGv_i32 val, TCGv addr, + TCGArg idx, MemOp memop) +{ + TCGMemExitMode exit_mode = select_mem_exit_mode(tcg_ctx); + + tcg_gen_qemu_st_i32_internal(tcg_ctx, val, addr, idx, memop, exit_mode); + gen_mem_exit_check(tcg_ctx, exit_mode); } void tcg_gen_qemu_ld_i64(TCGContext *tcg_ctx, TCGv_i64 val, TCGv addr, TCGArg idx, MemOp memop) { MemOp orig_memop; + TCGMemExitMode exit_mode = select_mem_exit_mode(tcg_ctx); #if TCG_TARGET_REG_BITS == 32 if ((memop & MO_SIZE) < MO_64) { - tcg_gen_qemu_ld_i32(tcg_ctx, TCGV_LOW(tcg_ctx, val), addr, idx, memop); + tcg_gen_qemu_ld_i32_internal(tcg_ctx, TCGV_LOW(tcg_ctx, val), addr, + idx, memop, exit_mode); if (memop & MO_SIGN) { tcg_gen_sari_i32(tcg_ctx, TCGV_HIGH(tcg_ctx, val), TCGV_LOW(tcg_ctx, val), 31); } else { tcg_gen_movi_i32(tcg_ctx, TCGV_HIGH(tcg_ctx, val), 0); } - check_exit_request(tcg_ctx); + gen_mem_exit_check(tcg_ctx, exit_mode); return; } #endif @@ -2986,7 +3056,8 @@ void tcg_gen_qemu_ld_i64(TCGContext *tcg_ctx, TCGv_i64 val, TCGv addr, TCGArg id } #endif - gen_ldst_i64(tcg_ctx, INDEX_op_qemu_ld_i64, val, addr, memop, idx); + gen_ldst_i64(tcg_ctx, INDEX_op_qemu_ld_i64, val, addr, memop, idx, + exit_mode); if ((orig_memop ^ memop) & MO_BSWAP) { switch (orig_memop & MO_SIZE) { @@ -3009,17 +3080,19 @@ void tcg_gen_qemu_ld_i64(TCGContext *tcg_ctx, TCGv_i64 val, TCGv addr, TCGArg id g_assert_not_reached(); } } - check_exit_request(tcg_ctx); + gen_mem_exit_check(tcg_ctx, exit_mode); } void tcg_gen_qemu_st_i64(TCGContext *tcg_ctx, TCGv_i64 val, TCGv addr, TCGArg idx, MemOp memop) { TCGv_i64 swap = NULL; + TCGMemExitMode exit_mode = select_mem_exit_mode(tcg_ctx); #if TCG_TARGET_REG_BITS == 32 if ((memop & MO_SIZE) < MO_64) { - tcg_gen_qemu_st_i32(tcg_ctx, TCGV_LOW(tcg_ctx, val), addr, idx, memop); - check_exit_request(tcg_ctx); + tcg_gen_qemu_st_i32_internal(tcg_ctx, TCGV_LOW(tcg_ctx, val), addr, + idx, memop, exit_mode); + gen_mem_exit_check(tcg_ctx, exit_mode); return; } #endif @@ -3050,12 +3123,13 @@ void tcg_gen_qemu_st_i64(TCGContext *tcg_ctx, TCGv_i64 val, TCGv addr, TCGArg id } #endif - gen_ldst_i64(tcg_ctx, INDEX_op_qemu_st_i64, val, addr, memop, idx); + gen_ldst_i64(tcg_ctx, INDEX_op_qemu_st_i64, val, addr, memop, idx, + exit_mode); if (swap) { tcg_temp_free_i64(tcg_ctx, swap); } - check_exit_request(tcg_ctx); + gen_mem_exit_check(tcg_ctx, exit_mode); } static void tcg_gen_ext_i32(TCGContext *tcg_ctx, TCGv_i32 ret, TCGv_i32 val, MemOp opc) diff --git a/qemu/tcg/tcg.c b/qemu/tcg/tcg.c index 458c85c418..141d9ce096 100644 --- a/qemu/tcg/tcg.c +++ b/qemu/tcg/tcg.c @@ -305,7 +305,7 @@ static int ptr_cmp_tb_tc(const void *ptr, const struct tb_tc *s) return 0; } -static gint tb_tc_cmp(gconstpointer ap, gconstpointer bp) +static gint tb_tc_cmp(gconstpointer ap, gconstpointer bp, gpointer user_data) { const struct tb_tc *a = ap; const struct tb_tc *b = bp; @@ -343,6 +343,7 @@ void tcg_tb_insert(TCGContext *tcg_ctx, TranslationBlock *tb) void tcg_tb_remove(TCGContext *tcg_ctx, TranslationBlock *tb) { + tcg_restore_state_cache_remove(tcg_ctx, tb); g_tree_remove(tcg_ctx->tree, &tb->tc); } @@ -376,6 +377,7 @@ size_t tcg_nb_tbs(TCGContext *tcg_ctx) static void tcg_region_tree_reset_all(TCGContext *tcg_ctx) { + tcg_restore_state_cache_clear(tcg_ctx); g_tree_ref(tcg_ctx->tree); g_tree_destroy(tcg_ctx->tree); } @@ -538,7 +540,7 @@ void tcg_region_init(TCGContext *tcg_ctx) (void)qemu_mprotect_none(end, page_size); } - tcg_ctx->tree = g_tree_new(tb_tc_cmp); + tcg_ctx->tree = g_tree_new_full(tb_tc_cmp, NULL, NULL, NULL); // Unicorn: Though this code is taken from CONFIG_USER_ONLY, it is crucial or // tcg_ctx->region.current is 0 and we will miss a tb_flush when the // buffer gets full. @@ -672,54 +674,96 @@ static void process_op_defs(TCGContext *s); static TCGTemp *tcg_global_reg_new_internal(TCGContext *s, TCGType type, TCGReg reg, const char *name); -void uc_add_inline_hook(uc_engine *uc, struct hook *hk, void** args, int args_len) +typedef struct UCInlineHookInfoKey { + void *func; + unsigned flags; + unsigned typemask; +} UCInlineHookInfoKey; + +static guint uc_inline_hook_info_hash(gconstpointer data) +{ + const UCInlineHookInfoKey *key = data; + uint64_t func = (uintptr_t)key->func; + + return (guint)(func ^ (func >> 32) ^ key->flags ^ key->typemask); +} + +static gboolean uc_inline_hook_info_equal(gconstpointer a, gconstpointer b) +{ + const UCInlineHookInfoKey *key_a = a; + const UCInlineHookInfoKey *key_b = b; + + return key_a->func == key_b->func && key_a->flags == key_b->flags && + key_a->typemask == key_b->typemask; +} + +static void uc_free_inline_hook_info(void *p) +{ + TCGHelperInfo *info = p; + + g_free((void *)info->name); + g_free(info); +} + +static TCGHelperInfo *uc_get_inline_hook_info(TCGContext *tcg_ctx, void *func, + unsigned flags, + unsigned typemask) { - TCGHelperInfo* info = g_malloc(sizeof(TCGHelperInfo)); - char *name = g_malloc(64); + UCInlineHookInfoKey lookup_key = { func, flags, typemask }; + UCInlineHookInfoKey *stored_key; + TCGHelperInfo *info; + + info = g_hash_table_lookup(tcg_ctx->custom_helper_infos, &lookup_key); + if (info == NULL) { + stored_key = g_new(UCInlineHookInfoKey, 1); + *stored_key = lookup_key; + + info = g_new(TCGHelperInfo, 1); + info->func = func; + info->name = g_strdup_printf("hookcode_%" PRIxPTR, (uintptr_t)func); + info->flags = flags; + info->typemask = typemask; + g_hash_table_insert(tcg_ctx->custom_helper_infos, stored_key, info); + } + + if (g_hash_table_lookup(tcg_ctx->helper_table, func) != info) { + g_hash_table_insert(tcg_ctx->helper_table, func, info); + } + + return info; +} + +void uc_add_inline_hook(uc_engine *uc, struct hook *hk, void **args, + int args_len) +{ + TCGHelperInfo *info; + unsigned flags = 0; unsigned typemask = 0xFFFFFFFF; TCGContext *tcg_ctx = uc->tcg_ctx; - GHashTable *helper_table = uc->tcg_ctx->helper_table; - - info->func = hk->callback; - info->name = name; - info->flags = 0; // From helper-head.h - // Only UC_HOOK_BLOCK and UC_HOOK_CODE is generated into tcg code and can be inlined. + /* Only block and code hooks are generated into TCG code. */ switch (hk->type) { case UC_HOOK_BLOCK: case UC_HOOK_CODE: - // (*uc_cb_hookcode_t)(uc_engine *uc, uint64_t address, uint32_t size, void *user_data); typemask = dh_typemask(void, 0) | dh_typemask(ptr, 1) | dh_typemask(i64, 2) | dh_typemask(i32, 3) | dh_typemask(ptr, 4); - snprintf(name, 63, "hookcode_%d_%" PRIxPTR , hk->type, (uintptr_t)hk->callback); break; default: break; } - name[63] = 0; - info->name = name; - info->typemask = typemask; - - g_hash_table_insert(helper_table, (gpointer)info->func, (gpointer)info); - g_hash_table_insert(uc->tcg_ctx->custom_helper_infos, (gpointer)info->func, (gpointer)info); - - tcg_gen_callN(tcg_ctx, info->func, NULL, args_len, (TCGTemp**)args); -} + info = uc_get_inline_hook_info(tcg_ctx, hk->callback, flags, typemask); -static void uc_free_inline_hook_info(void *p) -{ - TCGHelperInfo *info = (TCGHelperInfo *)p; - - g_free((void*)(info->name)); - g_free(info); + tcg_gen_callN(tcg_ctx, info->func, NULL, args_len, (TCGTemp **)args); } void uc_del_inline_hook(uc_engine *uc, struct hook *hk) { - g_hash_table_remove(uc->tcg_ctx->custom_helper_infos, hk->callback); + /* Metadata is shared by hooks and remains valid until engine teardown. */ + (void)uc; + (void)hk; } void tcg_context_init(TCGContext *s) @@ -764,8 +808,11 @@ void tcg_context_init(TCGContext *s) helper_table = g_hash_table_new(NULL, NULL); s->helper_table = helper_table; - // Unicorn: Store our custom inline hooks infomation - s->custom_helper_infos = g_hash_table_new_full(NULL, NULL, NULL, uc_free_inline_hook_info); + /* Cache custom inline hook metadata for the lifetime of the engine. */ + s->custom_helper_infos = + g_hash_table_new_full(uc_inline_hook_info_hash, + uc_inline_hook_info_equal, g_free, + uc_free_inline_hook_info); for (i = 0; i < ARRAY_SIZE(all_helpers); ++i) { g_hash_table_insert(helper_table, (gpointer)all_helpers[i].func, diff --git a/qemu/tricore.h b/qemu/tricore.h index 063367c81a..abeb318922 100644 --- a/qemu/tricore.h +++ b/qemu/tricore.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _tricore #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_tricore #define gen_helper_check_exit_request gen_helper_check_exit_request_tricore +#define gen_helper_exit_request gen_helper_exit_request_tricore +#define helper_check_counted_entry helper_check_counted_entry_tricore +#define helper_check_exit_request helper_check_exit_request_tricore +#define helper_exit_request helper_exit_request_tricore +#define helper_uc_tracecode_single helper_uc_tracecode_single_tricore +#define cpu_restore_icount cpu_restore_icount_tricore +#define cpu_tcg_exit_request cpu_tcg_exit_request_tricore #define unicorn_fill_tlb unicorn_fill_tlb_tricore #define reg_read reg_read_tricore #define reg_write reg_write_tricore @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_tricore #define flatview_translate flatview_translate_tricore #define flatview_copy flatview_copy_tricore +#define flatview_reserve flatview_reserve_tricore +#define address_space_restore_flatview address_space_restore_flatview_tricore #define address_space_translate_for_iotlb address_space_translate_for_iotlb_tricore #define qemu_get_cpu qemu_get_cpu_tricore #define cpu_address_space_init cpu_address_space_init_tricore @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_tricore #define memory_moveout memory_moveout_tricore #define memory_movein memory_movein_tricore +#define memory_mapping_restore_topology memory_mapping_restore_topology_tricore +#define memory_mapping_free memory_mapping_free_tricore +#define memory_mapping_prune memory_mapping_prune_tricore +#define memory_mapping_normalize memory_mapping_normalize_tricore #define memory_free memory_free_tricore #define flatview_unref flatview_unref_tricore #define address_space_get_flatview address_space_get_flatview_tricore @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_tricore #define tb_cleanup tb_cleanup_tricore #define tb_flush tb_flush_tricore +#define tb_flush_jit tb_flush_jit_tricore #define tb_phys_invalidate tb_phys_invalidate_tricore #define tb_gen_code tb_gen_code_tricore #define tb_reset_jump tb_reset_jump_tricore diff --git a/qemu/unicorn_common.h b/qemu/unicorn_common.h index 6bcfd32ea6..3196701a51 100644 --- a/qemu/unicorn_common.h +++ b/qemu/unicorn_common.h @@ -4,32 +4,34 @@ #include "tcg/tcg.h" #include "qemu-common.h" +#include "exec/exec-all.h" #include "exec/memory.h" #include "exec/cpu_ldst.h" +#include "accel/tcg/tb-exec-frame.h" -// This header define common patterns/codes that will be included in all arch-sepcific -// codes for unicorns purposes. +// This header define common patterns/codes that will be included in all +// arch-sepcific codes for unicorns purposes. -void vm_start(struct uc_struct*); +void vm_start(struct uc_struct *); void tcg_exec_init(struct uc_struct *uc, uint32_t tb_size); -bool unicorn_fill_tlb(CPUState *cs, vaddr address, int size, - MMUAccessType rw, int mmu_idx, - bool probe, uintptr_t retaddr); +bool unicorn_fill_tlb(CPUState *cs, vaddr address, int size, MMUAccessType rw, + int mmu_idx, bool probe, uintptr_t retaddr); // return true on success, false on failure static inline bool cpu_physical_mem_read(AddressSpace *as, hwaddr addr, - uint8_t *buf, hwaddr len) + uint8_t *buf, hwaddr len) { return cpu_physical_memory_rw(as, addr, (void *)buf, len, 0); } static inline bool cpu_physical_mem_write(AddressSpace *as, hwaddr addr, - const uint8_t *buf, hwaddr len) + const uint8_t *buf, hwaddr len) { return cpu_physical_memory_rw(as, addr, (void *)buf, len, 1); } -static bool cpu_virtual_mem_read(struct uc_struct *uc, vaddr addr, uint32_t prot, uint8_t *buf, int len) +static bool cpu_virtual_mem_read(struct uc_struct *uc, vaddr addr, + uint32_t prot, uint8_t *buf, int len) { MMUAccessType access_type; void *hostptr; @@ -41,7 +43,7 @@ static bool cpu_virtual_mem_read(struct uc_struct *uc, vaddr addr, uint32_t prot */ assert((addr & TARGET_PAGE_MASK) == ((addr + len - 1) & TARGET_PAGE_MASK)); - switch(prot) { + switch (prot) { case UC_PROT_READ: access_type = MMU_DATA_LOAD; break; @@ -63,13 +65,14 @@ static bool cpu_virtual_mem_read(struct uc_struct *uc, vaddr addr, uint32_t prot return true; } -static bool cpu_virtual_to_physical(struct uc_struct *uc, vaddr addr, uint32_t prot, uint64_t *paddr) +static bool cpu_virtual_to_physical(struct uc_struct *uc, vaddr addr, + uint32_t prot, uint64_t *paddr) { - target_ulong res; + hwaddr res; MMUAccessType access_type; int mmu_idx = cpu_mmu_index(uc->cpu->env_ptr, false); - switch(prot) { + switch (prot) { case UC_PROT_READ: access_type = MMU_DATA_LOAD; break; @@ -83,7 +86,8 @@ static bool cpu_virtual_to_physical(struct uc_struct *uc, vaddr addr, uint32_t p return false; } - if (!tlb_vaddr_to_paddr(uc->cpu->env_ptr, addr, access_type, mmu_idx, &res)) { + if (!tlb_vaddr_to_paddr(uc->cpu->env_ptr, addr, access_type, mmu_idx, + &res)) { return false; } @@ -108,7 +112,7 @@ static void release_common(void *t) cpu_breakpoint_remove_all(CPU(s->uc->cpu), BP_CPU); // Clean TCG. - TCGOpDef* def = s->tcg_op_defs; + TCGOpDef *def = s->tcg_op_defs; g_free(def->args_ct); g_free(def->sorted_args); g_free(s->tcg_op_defs); @@ -139,13 +143,13 @@ static void release_common(void *t) /* qemu/util/qht.c:264: map = qht_map_create(n_buckets); */ qht_destroy(&s->tb_ctx.htable); - #if TCG_TARGET_REG_BITS == 32 - for(i = 0; i < s->nb_globals; i++) { + for (i = 0; i < s->nb_globals; i++) { TCGTemp *ts = &s->temps[i]; if (ts->base_type == TCG_TYPE_I64) { - if (ts->name && ((strcmp(ts->name+(strlen(ts->name)-2), "_0") == 0) || - (strcmp(ts->name+(strlen(ts->name)-2), "_1") == 0))) { + if (ts->name && + ((strcmp(ts->name + (strlen(ts->name) - 2), "_0") == 0) || + (strcmp(ts->name + (strlen(ts->name) - 2), "_1") == 0))) { free((void *)ts->name); } } @@ -153,22 +157,25 @@ static void release_common(void *t) #endif } -static inline void target_page_init(struct uc_struct* uc) +static inline void target_page_init(struct uc_struct *uc) { uc->target_page_size = TARGET_PAGE_SIZE; uc->target_page_align = TARGET_PAGE_SIZE - 1; } -static uc_err uc_set_tlb(struct uc_struct *uc, int mode) { +static uc_err uc_set_tlb(struct uc_struct *uc, int mode) +{ switch (mode) { - case UC_TLB_VIRTUAL: - uc->cpu->cc->tlb_fill = unicorn_fill_tlb; - return UC_ERR_OK; - case UC_TLB_CPU: - uc->cpu->cc->tlb_fill = uc->cpu->cc->tlb_fill_cpu; - return UC_ERR_OK; - default: - return UC_ERR_ARG; + case UC_TLB_VIRTUAL: + uc->cpu->cc->tlb_fill = unicorn_fill_tlb; + uc->tlb_mode = UC_TLB_VIRTUAL; + return UC_ERR_OK; + case UC_TLB_CPU: + uc->cpu->cc->tlb_fill = uc->cpu->cc->tlb_fill_cpu; + uc->tlb_mode = UC_TLB_CPU; + return UC_ERR_OK; + default: + return UC_ERR_ARG; } } @@ -176,7 +183,9 @@ MemoryRegion *find_memory_mapping(struct uc_struct *uc, hwaddr address) { hwaddr xlat = 0; hwaddr len = 1; - MemoryRegion *mr = address_space_translate(&uc->address_space_memory, address, &xlat, &len, false, MEMTXATTRS_UNSPECIFIED); + MemoryRegion *mr = + address_space_translate(&uc->address_space_memory, address, &xlat, &len, + false, MEMTXATTRS_UNSPECIFIED); if (mr == &uc->io_mem_unassigned) { return NULL; @@ -185,7 +194,7 @@ MemoryRegion *find_memory_mapping(struct uc_struct *uc, hwaddr address) } void softfloat_init(void); -static inline void uc_common_init(struct uc_struct* uc) +static inline void uc_common_init(struct uc_struct *uc) { uc->write_mem = cpu_physical_mem_write; uc->read_mem = cpu_physical_mem_read; @@ -199,10 +208,18 @@ static inline void uc_common_init(struct uc_struct* uc) uc->memory_unmap = memory_unmap; uc->memory_moveout = memory_moveout; uc->memory_movein = memory_movein; + uc->memory_restore_topology = memory_mapping_restore_topology; + uc->memory_mapping_free = memory_mapping_free; + uc->memory_mapping_prune = memory_mapping_prune; + uc->memory_mapping_normalize = memory_mapping_normalize; uc->readonly_mem = memory_region_set_readonly; + uc->flatview_reserve = flatview_reserve; + uc->address_space_restore_flatview = address_space_restore_flatview; uc->target_page = target_page_init; uc->softfloat_initialize = softfloat_init; uc->tcg_flush_tlb = tcg_flush_softmmu_tlb; + uc->tb_exec_frame_resolve = tb_exec_frame_resolve; + uc->tb_exec_frame_publish = tb_exec_frame_publish_retaddr; uc->memory_map_io = memory_map_io; uc->set_tlb = uc_set_tlb; uc->memory_mapping = find_memory_mapping; @@ -214,26 +231,33 @@ static inline void uc_common_init(struct uc_struct* uc) uc->release = release_common; } -#define CHECK_REG_TYPE(type) do { \ - if (unlikely(*size < sizeof(type))) { \ - return UC_ERR_OVERFLOW; \ - } \ - *size = sizeof(type); \ - ret = UC_ERR_OK; \ -} while(0) - -#define CHECK_RET_DEPRECATE(ret, regid) do { \ - if (ret == UC_ERR_ARG && !getenv("UC_IGNORE_REG_BREAK")) { \ - fprintf(stderr, \ - "WARNING: Your register accessing on id %"PRIu32" is deprecated " \ - "and will get UC_ERR_ARG in the future release (2.2.0) because " \ - "the accessing is either no-op or not defined. If you believe " \ - "the register should be implemented or there is a bug, please " \ - "submit an issue to https://github.com/unicorn-engine/unicorn. " \ - "Set UC_IGNORE_REG_BREAK=1 to ignore this warning.\n", \ - regid); \ - ret = UC_ERR_OK; \ - } \ -} while (0) +#define CHECK_REG_TYPE(type) \ + do { \ + if (unlikely(*size < sizeof(type))) { \ + return UC_ERR_OVERFLOW; \ + } \ + *size = sizeof(type); \ + ret = UC_ERR_OK; \ + } while (0) + +#define CHECK_RET_DEPRECATE(ret, regid) \ + do { \ + if (ret == UC_ERR_ARG && !getenv("UC_IGNORE_REG_BREAK")) { \ + fprintf(stderr, \ + "WARNING: Your register accessing on id %" PRIu32 \ + " is deprecated " \ + "and will get UC_ERR_ARG in the future release (2.2.0) " \ + "because " \ + "the accessing is either no-op or not defined. If you " \ + "believe " \ + "the register should be implemented or there is a bug, " \ + "please " \ + "submit an issue to " \ + "https://github.com/unicorn-engine/unicorn. " \ + "Set UC_IGNORE_REG_BREAK=1 to ignore this warning.\n", \ + regid); \ + ret = UC_ERR_OK; \ + } \ + } while (0) #endif diff --git a/qemu/util/qemu-thread-posix.c b/qemu/util/qemu-thread-posix.c index ef6ce118c3..79252b59f3 100644 --- a/qemu/util/qemu-thread-posix.c +++ b/qemu/util/qemu-thread-posix.c @@ -34,14 +34,13 @@ int qemu_thread_create(struct uc_struct *uc, QemuThread *thread, const char *nam err = pthread_attr_init(&attr); if (err) { - error_exit(err, __func__); - return -1; + return err; } if (mode == QEMU_THREAD_DETACHED) { err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (err) { - error_exit(err, __func__); - return -1; + pthread_attr_destroy(&attr); + return err; } } @@ -50,16 +49,10 @@ int qemu_thread_create(struct uc_struct *uc, QemuThread *thread, const char *nam #endif pthread_sigmask(SIG_SETMASK, &set, &oldset); err = pthread_create(&thread->thread, &attr, start_routine, arg); - if (err) { - error_exit(err, __func__); - return -1; - } - pthread_sigmask(SIG_SETMASK, &oldset, NULL); - pthread_attr_destroy(&attr); - return 0; + return err; } void qemu_thread_exit(struct uc_struct *uc, void *retval) diff --git a/qemu/x86_64.h b/qemu/x86_64.h index dee75538c6..bdd3bcef19 100644 --- a/qemu/x86_64.h +++ b/qemu/x86_64.h @@ -4,7 +4,15 @@ #ifndef UNICORN_ARCH_POSTFIX #define UNICORN_ARCH_POSTFIX _x86_64 #endif +#define gen_helper_check_counted_entry gen_helper_check_counted_entry_x86_64 #define gen_helper_check_exit_request gen_helper_check_exit_request_x86_64 +#define gen_helper_exit_request gen_helper_exit_request_x86_64 +#define helper_check_counted_entry helper_check_counted_entry_x86_64 +#define helper_check_exit_request helper_check_exit_request_x86_64 +#define helper_exit_request helper_exit_request_x86_64 +#define helper_uc_tracecode_single helper_uc_tracecode_single_x86_64 +#define cpu_restore_icount cpu_restore_icount_x86_64 +#define cpu_tcg_exit_request cpu_tcg_exit_request_x86_64 #define unicorn_fill_tlb unicorn_fill_tlb_x86_64 #define reg_read reg_read_x86_64 #define reg_write reg_write_x86_64 @@ -56,6 +64,8 @@ #define address_space_dispatch_compact address_space_dispatch_compact_x86_64 #define flatview_translate flatview_translate_x86_64 #define flatview_copy flatview_copy_x86_64 +#define flatview_reserve flatview_reserve_x86_64 +#define address_space_restore_flatview address_space_restore_flatview_x86_64 #define address_space_translate_for_iotlb address_space_translate_for_iotlb_x86_64 #define qemu_get_cpu qemu_get_cpu_x86_64 #define cpu_address_space_init cpu_address_space_init_x86_64 @@ -131,6 +141,10 @@ #define memory_unmap memory_unmap_x86_64 #define memory_moveout memory_moveout_x86_64 #define memory_movein memory_movein_x86_64 +#define memory_mapping_restore_topology memory_mapping_restore_topology_x86_64 +#define memory_mapping_free memory_mapping_free_x86_64 +#define memory_mapping_prune memory_mapping_prune_x86_64 +#define memory_mapping_normalize memory_mapping_normalize_x86_64 #define memory_free memory_free_x86_64 #define flatview_unref flatview_unref_x86_64 #define address_space_get_flatview address_space_get_flatview_x86_64 @@ -1371,6 +1385,7 @@ #define tb_htable_init tb_htable_init_x86_64 #define tb_cleanup tb_cleanup_x86_64 #define tb_flush tb_flush_x86_64 +#define tb_flush_jit tb_flush_jit_x86_64 #define tb_phys_invalidate tb_phys_invalidate_x86_64 #define tb_gen_code tb_gen_code_x86_64 #define tb_reset_jump tb_reset_jump_x86_64 diff --git a/samples/Makefile b/samples/Makefile index cbb3d91fb7..e7710070df 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -64,11 +64,9 @@ UNICORN_ARCHS := $(shell if [ -e ../config.log ]; then cat ../config.log;\ SOURCES = ifneq (,$(findstring arm,$(UNICORN_ARCHS))) SOURCES += sample_arm.c -SOURCES += sample_armeb.c endif ifneq (,$(findstring aarch64,$(UNICORN_ARCHS))) SOURCES += sample_arm64.c -SOURCES += sample_arm64eb.c endif ifneq (,$(findstring mips,$(UNICORN_ARCHS))) SOURCES += sample_mips.c diff --git a/samples/sample_arm64.c b/samples/sample_arm64.c index 0dbaae736c..88128463b7 100644 --- a/samples/sample_arm64.c +++ b/samples/sample_arm64.c @@ -189,7 +189,7 @@ static void test_arm64eb(void) // now print out some registers printf(">>> Emulation done. Below is the CPU context\n"); - printf(">>> As big endian, X15 should be 0x78:\n"); + printf(">>> As big endian, X15 should be 0x12:\n"); uc_reg_read(uc, UC_ARM64_REG_X15, &x15); printf(">>> X15 = 0x%" PRIx64 "\n", x15); diff --git a/samples/sample_mmu.c b/samples/sample_mmu.c index 96c526bd74..545443aefd 100644 --- a/samples/sample_mmu.c +++ b/samples/sample_mmu.c @@ -26,7 +26,8 @@ static void mmu_write_callback(uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, void *user_data) { - printf("write at 0x%lx: 0x%lx\n", address, value); + printf("write at 0x%" PRIx64 ": 0x%" PRIx64 "\n", address, + (uint64_t)value); } static void x86_mmu_prepare_tlb(uc_engine *uc, uint64_t vaddr, @@ -280,8 +281,8 @@ void cpu_tlb(void) printf("failed to read from child memory\n"); exit(1); } - printf("parrent result == %lu\n", parrent); - printf("child result == %lu\n", child); + printf("parrent result == %" PRIu64 "\n", parrent); + printf("child result == %" PRIu64 "\n", child); uc_close(uc); } @@ -289,7 +290,7 @@ static bool virtual_tlb_callback(uc_engine *uc, uint64_t addr, uc_mem_type type, uc_tlb_entry *result, void *user_data) { bool *parrent_done = user_data; - printf("tlb lookup for address: 0x%lX\n", addr); + printf("tlb lookup for address: 0x%" PRIX64 "\n", addr); switch (addr & ~(0xfff)) { case 0x2000: result->paddr = 0x0; @@ -431,8 +432,8 @@ void virtual_tlb(void) printf("failed to read from child memory\n"); exit(1); } - printf("parrent result == %lu\n", parrent); - printf("child result == %lu\n", child); + printf("parrent result == %" PRIu64 "\n", parrent); + printf("child result == %" PRIu64 "\n", child); uc_close(uc); } diff --git a/samples/sample_riscv.c b/samples/sample_riscv.c index 4096d83b7a..f6bbadaf79 100644 --- a/samples/sample_riscv.c +++ b/samples/sample_riscv.c @@ -13,6 +13,7 @@ #endif // #define RISCV_CODE "\x13\x05\x10\x00\x93\x85\x05\x02\x93\x85\x05\x02" #define RISCV_CODE "\x13\x05\x10\x00\x93\x85\x05\x02" +#define RISCV_CODE_LOOP "\x6f\x00\x00\x00" // memory address where emulation starts #define ADDRESS 0x10000 @@ -291,12 +292,12 @@ static void test_riscv_step(void) static void test_riscv_timeout(void) { uc_engine *uc; - uc_hook trace1, trace2; uc_err err; uint32_t a0 = 0x1234; uint32_t a1 = 0x7890; uint32_t pc = 0x0000; + size_t timed_out; printf("Emulate RISCV code: timeout\n"); @@ -312,38 +313,44 @@ static void test_riscv_timeout(void) uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL); // write machine code to be emulated to memory - uc_mem_write(uc, ADDRESS, "\x00\x00\x00\x00\x00\x00\x00\x00", 8); + uc_mem_write(uc, ADDRESS, RISCV_CODE_LOOP, sizeof(RISCV_CODE_LOOP) - 1); // initialize machine registers uc_reg_write(uc, UC_RISCV_REG_A0, &a0); uc_reg_write(uc, UC_RISCV_REG_A1, &a1); - // tracing all basic blocks with customized callback - uc_hook_add(uc, &trace1, UC_HOOK_BLOCK, hook_block, NULL, 1, 0); - - // tracing all instruction - uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, 1, 0); - - // emulate 1 instruction with timeout - err = uc_emu_start(uc, ADDRESS, ADDRESS + 4, 1000, 1); + // emulate an infinite loop with timeout + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(RISCV_CODE_LOOP) - 1, + UC_SECOND_SCALE / 1000, 0); if (err) { printf("Failed on uc_emu_start() with error returned: %u\n", err); } + uc_query(uc, UC_QUERY_TIMEOUT, &timed_out); uc_reg_read(uc, UC_RISCV_REG_PC, &pc); - if (pc != 0x10000) { - printf("Error after step: PC is: 0x%x, expected was 0x10004\n", pc); + if (!timed_out) { + printf("Error: emulation did not time out\n"); + } + if (pc != ADDRESS) { + printf("Error after timeout: PC is: 0x%x, expected was 0x%x\n", pc, + ADDRESS); } - // emulate 1 instruction with timeout - err = uc_emu_start(uc, ADDRESS, ADDRESS + 4, 1000, 1); + // repeat to demonstrate that timeout emulation can be reused + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(RISCV_CODE_LOOP) - 1, + UC_SECOND_SCALE / 1000, 0); if (err) { printf("Failed on uc_emu_start() with error returned: %u\n", err); } + uc_query(uc, UC_QUERY_TIMEOUT, &timed_out); uc_reg_read(uc, UC_RISCV_REG_PC, &pc); - if (pc != 0x10000) { - printf("Error after step: PC is: 0x%x, expected was 0x10004\n", pc); + if (!timed_out) { + printf("Error: emulation did not time out\n"); + } + if (pc != ADDRESS) { + printf("Error after timeout: PC is: 0x%x, expected was 0x%x\n", pc, + ADDRESS); } // now print out some registers @@ -460,7 +467,7 @@ static void test_recover_from_illegal(void) // emulate 1 instruction, wrong address, illegal code err = uc_emu_start(uc, 0x1000, -1, 0, 1); - if (err != UC_ERR_INSN_INVALID) { + if (err != UC_ERR_EXCEPTION) { printf("Expected Illegal Instruction error, got: %u\n", err); } diff --git a/samples/shellcode.c b/samples/shellcode.c index 9094dc0785..bd2b5c5df1 100644 --- a/samples/shellcode.c +++ b/samples/shellcode.c @@ -151,7 +151,7 @@ int main(int argc, char **argv, char **envp) if (!strcmp(argv[1], "-32")) { test_i386(); } else if (!strcmp(argv[1], "-h")) { - printf("Syntax: %s <-32|-64>\n", argv[0]); + printf("Syntax: %s <-32>\n", argv[0]); } } else { test_i386(); diff --git a/symbols.sh b/symbols.sh index f9a16f2588..c20a36a87a 100755 --- a/symbols.sh +++ b/symbols.sh @@ -4,7 +4,15 @@ CMD_PATH=$(realpath $0) SOURCE_DIR=$(dirname ${CMD_PATH}) COMMON_SYMBOLS=" +gen_helper_check_counted_entry \ gen_helper_check_exit_request \ +gen_helper_exit_request \ +helper_check_counted_entry \ +helper_check_exit_request \ +helper_exit_request \ +helper_uc_tracecode_single \ +cpu_restore_icount \ +cpu_tcg_exit_request \ unicorn_fill_tlb \ reg_read \ reg_write \ @@ -56,6 +64,8 @@ vm_start \ address_space_dispatch_compact \ flatview_translate \ flatview_copy \ +flatview_reserve \ +address_space_restore_flatview \ address_space_translate_for_iotlb \ qemu_get_cpu \ cpu_address_space_init \ @@ -131,6 +141,10 @@ memory_cow \ memory_unmap \ memory_moveout \ memory_movein \ +memory_mapping_restore_topology \ +memory_mapping_free \ +memory_mapping_prune \ +memory_mapping_normalize \ memory_free \ flatview_unref \ address_space_get_flatview \ @@ -1371,6 +1385,7 @@ tcg_exec_init \ tb_htable_init \ tb_cleanup \ tb_flush \ +tb_flush_jit \ tb_phys_invalidate \ tb_gen_code \ tb_reset_jump \ @@ -7411,6 +7426,7 @@ riscv_cpu_debug_excp_handler \ riscv_cpu_debug_check_breakpoint \ riscv_cpu_debug_check_watchpoint \ riscv_trigger_init \ +riscv_trigger_update_all \ riscv_cpu_get_fflags \ riscv_cpu_set_fflags \ helper_set_rounding_mode \ diff --git a/tests/benchmarks/cow/benchmark.c b/tests/benchmarks/cow/benchmark.c index 265959acf2..0d09279222 100644 --- a/tests/benchmarks/cow/benchmark.c +++ b/tests/benchmarks/cow/benchmark.c @@ -26,7 +26,7 @@ static uint64_t DATABASE = 0x40000000; static uint64_t BLOCKSIZE = 0x10000; static size_t NRUNS = 200; -static int callback_mem_prot(uc_engine *uc, uc_mem_type type, uint64_t addr, uint32_t size, int64_t value, void *data) +static bool callback_mem_prot(uc_engine *uc, uc_mem_type type, uint64_t addr, int size, int64_t value, void *data) { printf("callback mem prot: 0x%lX, type: %X\n", addr, type); return false; diff --git a/tests/benchmarks/hooks/CMakeLists.txt b/tests/benchmarks/hooks/CMakeLists.txt new file mode 100644 index 0000000000..7966bd3364 --- /dev/null +++ b/tests/benchmarks/hooks/CMakeLists.txt @@ -0,0 +1,82 @@ +cmake_minimum_required(VERSION 3.15) + +project(unicorn_hook_benchmark C) + +if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) +endif() + +get_filename_component(_default_source_dir + "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) +set(UNICORN_SOURCE_DIR "${_default_source_dir}" CACHE PATH + "Path to the Unicorn source tree") +set(UNICORN_BUILD_DIR "${UNICORN_SOURCE_DIR}/build" CACHE PATH + "Path to an existing CMake-produced Unicorn build") + +set(_unicorn_hints + "${UNICORN_BUILD_DIR}" + "${UNICORN_BUILD_DIR}/Release" + "${UNICORN_BUILD_DIR}/RelWithDebInfo" + "${UNICORN_BUILD_DIR}/Debug") + +find_path(UNICORN_INCLUDE_DIR unicorn/unicorn.h + HINTS "${UNICORN_SOURCE_DIR}/include") + +if(WIN32) + if(MSVC) + find_library(UNICORN_LIBRARY NAMES unicorn-import unicorn + HINTS ${_unicorn_hints}) + else() + find_library(UNICORN_LIBRARY NAMES unicorn + HINTS ${_unicorn_hints}) + endif() + find_file(UNICORN_RUNTIME NAMES unicorn.dll + HINTS ${_unicorn_hints}) + if(NOT UNICORN_LIBRARY OR NOT UNICORN_RUNTIME) + message(FATAL_ERROR + "UNICORN_BUILD_DIR must point to a shared Unicorn build") + endif() + add_library(unicorn_built SHARED IMPORTED) + set_target_properties(unicorn_built PROPERTIES + IMPORTED_IMPLIB "${UNICORN_LIBRARY}" + IMPORTED_LOCATION "${UNICORN_RUNTIME}") +else() + find_library(UNICORN_LIBRARY NAMES unicorn HINTS ${_unicorn_hints}) + if(NOT UNICORN_LIBRARY) + message(FATAL_ERROR + "No Unicorn library found below UNICORN_BUILD_DIR") + endif() + add_library(unicorn_built UNKNOWN IMPORTED) + set_target_properties(unicorn_built PROPERTIES + IMPORTED_LOCATION "${UNICORN_LIBRARY}") +endif() + +if(NOT UNICORN_INCLUDE_DIR) + message(FATAL_ERROR "No Unicorn headers found below UNICORN_SOURCE_DIR") +endif() + +add_executable(hook_bench benchmark.c) +target_compile_features(hook_bench PRIVATE c_std_99) +target_include_directories(hook_bench SYSTEM PRIVATE "${UNICORN_INCLUDE_DIR}") +target_link_libraries(hook_bench PRIVATE unicorn_built) + +set_target_properties(hook_bench PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +foreach(_configuration Debug Release RelWithDebInfo MinSizeRel) + string(TOUPPER "${_configuration}" _configuration_upper) + set_target_properties(hook_bench PROPERTIES + "RUNTIME_OUTPUT_DIRECTORY_${_configuration_upper}" + "${CMAKE_BINARY_DIR}/bin") +endforeach() + +if(MSVC) + target_compile_options(hook_bench PRIVATE /W4) +else() + target_compile_options(hook_bench PRIVATE -Wall -Wextra) +endif() + +if(WIN32) + add_custom_command(TARGET hook_bench POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${UNICORN_RUNTIME}" "$") +endif() diff --git a/tests/benchmarks/hooks/benchmark.c b/tests/benchmarks/hooks/benchmark.c new file mode 100644 index 0000000000..cb5efce90d --- /dev/null +++ b/tests/benchmarks/hooks/benchmark.c @@ -0,0 +1,325 @@ +#ifndef _WIN32 +#define _POSIX_C_SOURCE 200809L +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include +#include + +#define CODE_ADDRESS UINT64_C(0x100000) +#define DATA_ADDRESS UINT64_C(0x200000) +#define DEFAULT_ITERATIONS UINT64_C(1000000) +#define DEFAULT_REPEATS 7 +#define MAX_HOOKS 8 + +typedef enum BenchmarkMode { + MODE_NONE, + MODE_COUNT, + MODE_CODE_ONE, + MODE_CODE_EIGHT, + MODE_CODE_TAIL, + MODE_BLOCK_ONE, + MODE_MEMORY_NONE, + MODE_MEMORY_ONE, + MODE_MEMORY_EIGHT, + MODE_MEMORY_TAIL, +} BenchmarkMode; + +typedef struct BenchmarkDefinition { + const char *name; + BenchmarkMode mode; +} BenchmarkDefinition; + +static const BenchmarkDefinition definitions[] = { + { "none", MODE_NONE }, + { "count", MODE_COUNT }, + { "code-one", MODE_CODE_ONE }, + { "code-eight", MODE_CODE_EIGHT }, + { "code-tail", MODE_CODE_TAIL }, + { "block-one", MODE_BLOCK_ONE }, + { "memory-none", MODE_MEMORY_NONE }, + { "memory-one", MODE_MEMORY_ONE }, + { "memory-eight", MODE_MEMORY_EIGHT }, + { "memory-tail", MODE_MEMORY_TAIL }, +}; + +static volatile uint64_t callback_count; + +static void fail_uc(uc_engine *uc, const char *operation, uc_err error) +{ + fprintf(stderr, "%s: %s\n", operation, uc_strerror(error)); + if (uc != NULL) { + uc_close(uc); + } + exit(1); +} + +static void check_uc(uc_engine *uc, const char *operation, uc_err error) +{ + if (error != UC_ERR_OK) { + fail_uc(uc, operation, error); + } +} + +static void code_callback(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + (void)uc; + (void)address; + (void)size; + (void)user_data; + callback_count++; +} + +static void memory_callback(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, + void *user_data) +{ + (void)uc; + (void)type; + (void)address; + (void)size; + (void)value; + (void)user_data; + callback_count++; +} + +static double monotonic_seconds(void) +{ +#ifdef _WIN32 + LARGE_INTEGER counter; + LARGE_INTEGER frequency; + + QueryPerformanceCounter(&counter); + QueryPerformanceFrequency(&frequency); + return (double)counter.QuadPart / (double)frequency.QuadPart; +#else + struct timespec time; + + clock_gettime(CLOCK_MONOTONIC, &time); + return (double)time.tv_sec + (double)time.tv_nsec / 1000000000.0; +#endif +} + +static int compare_double(const void *lhs, const void *rhs) +{ + double a = *(const double *)lhs; + double b = *(const double *)rhs; + + return (a > b) - (a < b); +} + +static const BenchmarkDefinition *find_definition(const char *name) +{ + size_t i; + + for (i = 0; i < sizeof(definitions) / sizeof(definitions[0]); i++) { + if (strcmp(name, definitions[i].name) == 0) { + return &definitions[i]; + } + } + return NULL; +} + +static bool is_memory_mode(BenchmarkMode mode) +{ + return mode >= MODE_MEMORY_NONE; +} + +static uint64_t expected_callbacks(BenchmarkMode mode, uint64_t iterations, + size_t repeats) +{ + uint64_t per_run; + + switch (mode) { + case MODE_CODE_ONE: + case MODE_CODE_TAIL: + per_run = iterations * 2; + break; + case MODE_CODE_EIGHT: + per_run = iterations * 16; + break; + case MODE_BLOCK_ONE: + case MODE_MEMORY_ONE: + case MODE_MEMORY_TAIL: + per_run = iterations; + break; + case MODE_MEMORY_EIGHT: + per_run = iterations * 8; + break; + default: + per_run = 0; + break; + } + return per_run * repeats; +} + +static void add_code_hooks(uc_engine *uc, BenchmarkMode mode, + uc_hook hooks[MAX_HOOKS]) +{ + int count = mode == MODE_CODE_ONE || mode == MODE_BLOCK_ONE ? 1 : 8; + int type = mode == MODE_BLOCK_ONE ? UC_HOOK_BLOCK : UC_HOOK_CODE; + int i; + + if (mode == MODE_NONE || mode == MODE_COUNT) { + return; + } + for (i = 0; i < count; i++) { + uint64_t begin = 1; + uint64_t end = 0; + + if (mode == MODE_CODE_TAIL && i != count - 1) { + begin = CODE_ADDRESS + 0x1000 + (uint64_t)i * 0x10; + end = begin; + } + check_uc(uc, "uc_hook_add(code)", + uc_hook_add(uc, &hooks[i], type, code_callback, NULL, + begin, end)); + } +} + +static void add_memory_hooks(uc_engine *uc, BenchmarkMode mode, + uc_hook hooks[MAX_HOOKS]) +{ + int count = mode == MODE_MEMORY_ONE ? 1 : 8; + int i; + + if (mode == MODE_MEMORY_NONE) { + return; + } + for (i = 0; i < count; i++) { + uint64_t begin = DATA_ADDRESS; + uint64_t end = DATA_ADDRESS + 7; + + if (mode == MODE_MEMORY_TAIL && i != count - 1) { + begin += 0x1000 + (uint64_t)i * 0x10; + end = begin + 7; + } + check_uc(uc, "uc_hook_add(memory)", + uc_hook_add(uc, &hooks[i], UC_HOOK_MEM_READ, + memory_callback, NULL, begin, end)); + } +} + +static uc_engine *create_engine(const BenchmarkDefinition *definition, + uint64_t iterations) +{ + static const uint8_t code_loop[] = { + 0x48, 0xff, 0xc9, /* dec rcx */ + 0x75, 0xfb, /* jne loop */ + }; + static const uint8_t memory_loop[] = { + 0x48, 0x8b, 0x03, /* mov rax, [rbx] */ + 0x48, 0xff, 0xc9, /* dec rcx */ + 0x75, 0xf8, /* jne loop */ + }; + const uint8_t *code = code_loop; + size_t code_size = sizeof(code_loop); + uint64_t data = UINT64_C(0x1122334455667788); + uint64_t rbx = DATA_ADDRESS; + uc_hook hooks[MAX_HOOKS]; + uc_engine *uc = NULL; + + if (is_memory_mode(definition->mode)) { + code = memory_loop; + code_size = sizeof(memory_loop); + } + check_uc(uc, "uc_open", uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + check_uc(uc, "uc_mem_map(code)", + uc_mem_map(uc, CODE_ADDRESS, 0x1000, UC_PROT_ALL)); + check_uc(uc, "uc_mem_write(code)", + uc_mem_write(uc, CODE_ADDRESS, code, code_size)); + check_uc(uc, "uc_mem_map(data)", + uc_mem_map(uc, DATA_ADDRESS, 0x1000, UC_PROT_ALL)); + check_uc(uc, "uc_mem_write(data)", + uc_mem_write(uc, DATA_ADDRESS, &data, sizeof(data))); + check_uc(uc, "uc_reg_write(rbx)", + uc_reg_write(uc, UC_X86_REG_RBX, &rbx)); + if (is_memory_mode(definition->mode)) { + add_memory_hooks(uc, definition->mode, hooks); + } else { + add_code_hooks(uc, definition->mode, hooks); + } + (void)iterations; + return uc; +} + +static double run_once(uc_engine *uc, const BenchmarkDefinition *definition, + uint64_t iterations) +{ + const uint64_t instruction_count = iterations * 2; + const size_t code_size = is_memory_mode(definition->mode) ? 8 : 5; + uint64_t rcx = iterations; + double begin; + double end; + + check_uc(uc, "uc_reg_write(rcx)", + uc_reg_write(uc, UC_X86_REG_RCX, &rcx)); + begin = monotonic_seconds(); + check_uc(uc, "uc_emu_start", + uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + code_size, 0, + definition->mode == MODE_COUNT ? + instruction_count : 0)); + end = monotonic_seconds(); + return (end - begin) * 1000.0; +} + +int main(int argc, char **argv) +{ + const BenchmarkDefinition *definition; + uint64_t iterations = DEFAULT_ITERATIONS; + size_t repeats = DEFAULT_REPEATS; + double *samples; + uc_engine *uc; + size_t i; + + if (argc < 2 || argc > 4 || + (definition = find_definition(argv[1])) == NULL) { + fprintf(stderr, "usage: hook_bench MODE [ITERATIONS] [REPEATS]\n"); + return 2; + } + if (argc >= 3) { + iterations = strtoull(argv[2], NULL, 0); + } + if (argc >= 4) { + repeats = strtoul(argv[3], NULL, 0); + } + if (iterations == 0 || repeats == 0) { + return 2; + } + samples = calloc(repeats, sizeof(*samples)); + if (samples == NULL) { + return 1; + } + uc = create_engine(definition, iterations); + (void)run_once(uc, definition, 1000); + callback_count = 0; + for (i = 0; i < repeats; i++) { + samples[i] = run_once(uc, definition, iterations); + } + if (callback_count != + expected_callbacks(definition->mode, iterations, repeats)) { + fprintf(stderr, "unexpected callback count: %" PRIu64 "\n", + callback_count); + return 1; + } + qsort(samples, repeats, sizeof(*samples), compare_double); + printf("mode=%s iterations=%" PRIu64 " repeats=%zu median_ms=%.3f " + "callbacks=%" PRIu64 "\n", definition->name, iterations, + repeats, samples[repeats / 2], callback_count); + check_uc(NULL, "uc_close", uc_close(uc)); + free(samples); + return 0; +} diff --git a/tests/benchmarks/tlb/CMakeLists.txt b/tests/benchmarks/tlb/CMakeLists.txt new file mode 100644 index 0000000000..8d7f8db5a2 --- /dev/null +++ b/tests/benchmarks/tlb/CMakeLists.txt @@ -0,0 +1,82 @@ +cmake_minimum_required(VERSION 3.15) + +project(unicorn_tlb_benchmark C) + +if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) +endif() + +get_filename_component(_default_source_dir + "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) +set(UNICORN_SOURCE_DIR "${_default_source_dir}" CACHE PATH + "Path to the Unicorn source tree") +set(UNICORN_BUILD_DIR "${UNICORN_SOURCE_DIR}/build" CACHE PATH + "Path to an existing CMake-produced Unicorn build") + +set(_unicorn_hints + "${UNICORN_BUILD_DIR}" + "${UNICORN_BUILD_DIR}/Release" + "${UNICORN_BUILD_DIR}/RelWithDebInfo" + "${UNICORN_BUILD_DIR}/Debug") + +find_path(UNICORN_INCLUDE_DIR unicorn/unicorn.h + HINTS "${UNICORN_SOURCE_DIR}/include") + +if(WIN32) + if(MSVC) + find_library(UNICORN_LIBRARY NAMES unicorn-import unicorn + HINTS ${_unicorn_hints}) + else() + find_library(UNICORN_LIBRARY NAMES unicorn + HINTS ${_unicorn_hints}) + endif() + find_file(UNICORN_RUNTIME NAMES unicorn.dll + HINTS ${_unicorn_hints}) + if(NOT UNICORN_LIBRARY OR NOT UNICORN_RUNTIME) + message(FATAL_ERROR + "UNICORN_BUILD_DIR must point to a shared Unicorn build") + endif() + add_library(unicorn_built SHARED IMPORTED) + set_target_properties(unicorn_built PROPERTIES + IMPORTED_IMPLIB "${UNICORN_LIBRARY}" + IMPORTED_LOCATION "${UNICORN_RUNTIME}") +else() + find_library(UNICORN_LIBRARY NAMES unicorn HINTS ${_unicorn_hints}) + if(NOT UNICORN_LIBRARY) + message(FATAL_ERROR + "No Unicorn library found below UNICORN_BUILD_DIR") + endif() + add_library(unicorn_built UNKNOWN IMPORTED) + set_target_properties(unicorn_built PROPERTIES + IMPORTED_LOCATION "${UNICORN_LIBRARY}") +endif() + +if(NOT UNICORN_INCLUDE_DIR) + message(FATAL_ERROR "No Unicorn headers found below UNICORN_SOURCE_DIR") +endif() + +add_executable(tlb_bench benchmark.c) +target_compile_features(tlb_bench PRIVATE c_std_99) +target_include_directories(tlb_bench SYSTEM PRIVATE "${UNICORN_INCLUDE_DIR}") +target_link_libraries(tlb_bench PRIVATE unicorn_built) + +set_target_properties(tlb_bench PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +foreach(_configuration Debug Release RelWithDebInfo MinSizeRel) + string(TOUPPER "${_configuration}" _configuration_upper) + set_target_properties(tlb_bench PROPERTIES + "RUNTIME_OUTPUT_DIRECTORY_${_configuration_upper}" + "${CMAKE_BINARY_DIR}/bin") +endforeach() + +if(MSVC) + target_compile_options(tlb_bench PRIVATE /W4) +else() + target_compile_options(tlb_bench PRIVATE -Wall -Wextra) +endif() + +if(WIN32) + add_custom_command(TARGET tlb_bench POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${UNICORN_RUNTIME}" "$") +endif() diff --git a/tests/benchmarks/tlb/README.md b/tests/benchmarks/tlb/README.md new file mode 100644 index 0000000000..2dbbb30587 --- /dev/null +++ b/tests/benchmarks/tlb/README.md @@ -0,0 +1,55 @@ +# x86-64 TLB benchmark + +This public-API benchmark provides six modes: + +- `dense`: sequential 64-bit loads through a 64 KiB ring. +- `hook-global`: the dense workload with a global `UC_HOOK_MEM_READ`. +- `hook-bounded`: the dense workload with a read hook on the first 4 KiB. +- `vtlb-dense`: the dense workload with identity-mapped `UC_TLB_VIRTUAL`. +- `vtlb-conflict`: loads from 16 identity-mapped pages spaced 1 MiB apart. +- `vtlb-conflict-cold`: the same conflict pattern with a fresh engine for + every sample, including TLB growth cost. + +Each timed repeat starts after a TLB flush. Warmups populate the translation +cache, and the summary reports median/min/max time, throughput, hook calls, and +TLB fill counts. Setup and TLB flush time are excluded. + +## Build + +First build Unicorn as a shared library with CMake. Then configure this +standalone benchmark against that build; the repository root CMake files do +not need changes. + +Windows PowerShell, using an existing MSVC or Ninja build: + +```powershell +cmake -S .\tests\benchmarks\tlb -B .\tests\benchmarks\tlb\build ` + -DUNICORN_BUILD_DIR="$PWD\build_qemu72_tests" ` + -DCMAKE_BUILD_TYPE=Release +cmake --build .\tests\benchmarks\tlb\build --config Release +.\tests\benchmarks\tlb\build\bin\tlb_bench.exe all +``` + +Linux, from the repository root: + +```sh +cmake -S tests/benchmarks/tlb -B tests/benchmarks/tlb/build \ + -DUNICORN_BUILD_DIR="$PWD/build" -DCMAKE_BUILD_TYPE=Release +cmake --build tests/benchmarks/tlb/build -j +LD_LIBRARY_PATH="$PWD/build${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \ + tests/benchmarks/tlb/build/bin/tlb_bench all +``` + +## Run + +Select one mode or `all`, then optionally set warmups, timed repeats, and guest +load count: + +```text +tlb_bench [all|dense|hook-global|hook-bounded|vtlb-dense|vtlb-conflict| + vtlb-conflict-cold] + [--warmup N] [--repeats N] [--loads N] +``` + +Dense modes require `--loads` to be a multiple of eight. Defaults are two +warmups, seven repeats, and 1,048,576 loads. diff --git a/tests/benchmarks/tlb/benchmark.c b/tests/benchmarks/tlb/benchmark.c new file mode 100644 index 0000000000..e7a9406646 --- /dev/null +++ b/tests/benchmarks/tlb/benchmark.c @@ -0,0 +1,640 @@ +#ifndef _WIN32 +#define _POSIX_C_SOURCE 200809L +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include +#include + +#define CODE_ADDRESS UINT64_C(0x00100000) +#define DATA_ADDRESS UINT64_C(0x02001000) +#define PAGE_SIZE UINT64_C(0x1000) +#define DENSE_SIZE (PAGE_SIZE * UINT64_C(16)) +#define CONFLICT_STRIDE (PAGE_SIZE * UINT64_C(256)) +#define CONFLICT_PAGES UINT64_C(16) +#define DATA_MAP_SIZE (CONFLICT_STRIDE * CONFLICT_PAGES) +#define DENSE_LOADS_PER_ITERATION UINT64_C(8) + +#define DEFAULT_WARMUP 2 +#define DEFAULT_REPEATS 7 +#define DEFAULT_LOADS UINT64_C(1048576) + +typedef enum ReadHookMode { + READ_HOOK_NONE, + READ_HOOK_GLOBAL, + READ_HOOK_BOUNDED, +} ReadHookMode; + +typedef struct BenchmarkDefinition { + const char *name; + ReadHookMode read_hook; + bool virtual_tlb; + bool conflict_access; + bool recreate_engine; +} BenchmarkDefinition; + +typedef struct CallbackCounts { + uint64_t hook_reads; + uint64_t tlb_fills; + uint64_t read_fills; + uint64_t fetch_fills; +} CallbackCounts; + +typedef struct RunSample { + double milliseconds; + CallbackCounts counts; +} RunSample; + +typedef struct Options { + const char *mode; + size_t warmup; + size_t repeats; + uint64_t loads; +} Options; + +typedef struct CounterRange { + uint64_t minimum; + uint64_t maximum; +} CounterRange; + +static const BenchmarkDefinition benchmark_definitions[] = { + { "dense", READ_HOOK_NONE, false, false, false }, + { "hook-global", READ_HOOK_GLOBAL, false, false, false }, + { "hook-bounded", READ_HOOK_BOUNDED, false, false, false }, + { "vtlb-dense", READ_HOOK_NONE, true, false, false }, + { "vtlb-conflict", READ_HOOK_NONE, true, true, false }, + { "vtlb-conflict-cold", READ_HOOK_NONE, true, true, true }, +}; + +/* + * Eight adjacent 64-bit loads, followed by a 64-byte step through a 64 KiB + * ring. RCX contains the number of eight-load groups. + */ +static const uint8_t dense_code[] = { + 0x48, 0x8b, 0x06, + 0x48, 0x8b, 0x46, 0x08, + 0x48, 0x8b, 0x46, 0x10, + 0x48, 0x8b, 0x46, 0x18, + 0x48, 0x8b, 0x46, 0x20, + 0x48, 0x8b, 0x46, 0x28, + 0x48, 0x8b, 0x46, 0x30, + 0x48, 0x8b, 0x46, 0x38, + 0x48, 0x83, 0xc6, 0x40, + 0x48, 0x39, 0xfe, + 0x75, 0x03, + 0x48, 0x89, 0xde, + 0x48, 0xff, 0xc9, + 0x75, 0xd0, +}; + +/* + * One load from each of 16 pages separated by 1 MiB. The pages have the same + * low virtual-page index and outnumber the QEMU victim TLB entries. + */ +static const uint8_t conflict_code[] = { + 0x48, 0x8b, 0x06, + 0x48, 0x81, 0xc6, 0x00, 0x00, 0x10, 0x00, + 0x48, 0x39, 0xfe, + 0x75, 0x03, + 0x48, 0x89, 0xde, + 0x48, 0xff, 0xc9, + 0x75, 0xe9, +}; + +static double monotonic_seconds(void) +{ +#ifdef _WIN32 + static LARGE_INTEGER frequency; + static bool initialized; + LARGE_INTEGER counter; + + if (!initialized) { + QueryPerformanceFrequency(&frequency); + initialized = true; + } + QueryPerformanceCounter(&counter); + return (double)counter.QuadPart / (double)frequency.QuadPart; +#else + struct timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + return (double)now.tv_sec + (double)now.tv_nsec / 1000000000.0; +#endif +} + +static void mem_read_callback(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, + void *user_data) +{ + CallbackCounts *counts = user_data; + + (void)uc; + (void)type; + (void)address; + (void)size; + (void)value; + counts->hook_reads++; +} + +static bool tlb_fill_callback(uc_engine *uc, uint64_t address, + uc_mem_type type, uc_tlb_entry *result, + void *user_data) +{ + CallbackCounts *counts = user_data; + + (void)uc; + counts->tlb_fills++; + if (type == UC_MEM_READ) { + counts->read_fills++; + } else if (type == UC_MEM_FETCH) { + counts->fetch_fills++; + } + + result->paddr = address; + result->perms = UC_PROT_ALL; + return true; +} + +static bool report_uc_error(const char *operation, uc_err err) +{ + if (err == UC_ERR_OK) { + return true; + } + + fprintf(stderr, "%s failed: %s\n", operation, uc_strerror(err)); + return false; +} + +static const BenchmarkDefinition *find_benchmark(const char *name) +{ + size_t i; + + for (i = 0; i < sizeof(benchmark_definitions) / + sizeof(benchmark_definitions[0]); i++) { + if (strcmp(name, benchmark_definitions[i].name) == 0) { + return &benchmark_definitions[i]; + } + } + return NULL; +} + +static bool parse_u64(const char *text, uint64_t *value) +{ + char *end; + unsigned long long parsed; + + if (*text == '-') { + return false; + } + + errno = 0; + end = NULL; + parsed = strtoull(text, &end, 10); + if (errno != 0 || end == text || *end != '\0') { + return false; + } + *value = (uint64_t)parsed; + return true; +} + +static void print_usage(const char *program) +{ + fprintf(stderr, + "usage: %s [all|dense|hook-global|hook-bounded|vtlb-dense|" + "vtlb-conflict|vtlb-conflict-cold]\n" + " [--warmup N] [--repeats N] [--loads N]\n", + program); +} + +static int parse_arguments(int argc, char **argv, Options *options) +{ + int argument; + + options->mode = "all"; + options->warmup = DEFAULT_WARMUP; + options->repeats = DEFAULT_REPEATS; + options->loads = DEFAULT_LOADS; + argument = 1; + + if (argument < argc && argv[argument][0] != '-') { + options->mode = argv[argument++]; + } + + while (argument < argc) { + const char *name = argv[argument++]; + uint64_t parsed; + + if (strcmp(name, "--help") == 0 || strcmp(name, "-h") == 0) { + print_usage(argv[0]); + return 1; + } + if (argument >= argc) { + fprintf(stderr, "missing value after %s\n", name); + return -1; + } + if (!parse_u64(argv[argument++], &parsed)) { + fprintf(stderr, "invalid numeric value for %s\n", name); + return -1; + } + + if (strcmp(name, "--warmup") == 0) { + if (parsed > SIZE_MAX) { + fprintf(stderr, "--warmup is too large\n"); + return -1; + } + options->warmup = (size_t)parsed; + } else if (strcmp(name, "--repeats") == 0) { + if (parsed == 0 || parsed > SIZE_MAX) { + fprintf(stderr, "--repeats must be between 1 and SIZE_MAX\n"); + return -1; + } + options->repeats = (size_t)parsed; + } else if (strcmp(name, "--loads") == 0) { + if (parsed == 0) { + fprintf(stderr, "--loads must be greater than zero\n"); + return -1; + } + options->loads = parsed; + } else { + fprintf(stderr, "unknown option: %s\n", name); + return -1; + } + } + + if (strcmp(options->mode, "all") != 0 && + find_benchmark(options->mode) == NULL) { + fprintf(stderr, "unknown mode: %s\n", options->mode); + return -1; + } + return 0; +} + +static bool setup_engine(const BenchmarkDefinition *definition, + CallbackCounts *counts, uc_engine **result) +{ + const uint8_t *code; + size_t code_size; + uint64_t hook_begin; + uint64_t hook_end; + uc_engine *uc; + uc_hook hook; + uc_err err; + + uc = NULL; + err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc); + if (!report_uc_error("uc_open", err)) { + return false; + } + + if (definition->virtual_tlb) { + err = uc_ctl_tlb_mode(uc, UC_TLB_VIRTUAL); + if (!report_uc_error("uc_ctl_tlb_mode", err)) { + uc_close(uc); + return false; + } + err = uc_hook_add(uc, &hook, UC_HOOK_TLB_FILL, + (void *)tlb_fill_callback, counts, 1, 0); + if (!report_uc_error("uc_hook_add(UC_HOOK_TLB_FILL)", err)) { + uc_close(uc); + return false; + } + } + + err = uc_mem_map(uc, CODE_ADDRESS, PAGE_SIZE, + UC_PROT_READ | UC_PROT_EXEC); + if (!report_uc_error("uc_mem_map(code)", err)) { + uc_close(uc); + return false; + } + err = uc_mem_map(uc, DATA_ADDRESS, DATA_MAP_SIZE, UC_PROT_READ); + if (!report_uc_error("uc_mem_map(data)", err)) { + uc_close(uc); + return false; + } + + if (definition->conflict_access) { + code = conflict_code; + code_size = sizeof(conflict_code); + } else { + code = dense_code; + code_size = sizeof(dense_code); + } + err = uc_mem_write(uc, CODE_ADDRESS, code, code_size); + if (!report_uc_error("uc_mem_write(code)", err)) { + uc_close(uc); + return false; + } + + if (definition->read_hook != READ_HOOK_NONE) { + if (definition->read_hook == READ_HOOK_GLOBAL) { + hook_begin = 1; + hook_end = 0; + } else { + hook_begin = DATA_ADDRESS; + hook_end = DATA_ADDRESS + PAGE_SIZE - 1; + } + err = uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, + (void *)mem_read_callback, counts, hook_begin, + hook_end); + if (!report_uc_error("uc_hook_add(UC_HOOK_MEM_READ)", err)) { + uc_close(uc); + return false; + } + } + + *result = uc; + return true; +} + +static bool run_once(uc_engine *uc, const BenchmarkDefinition *definition, + uint64_t loads, double *milliseconds) +{ + uint64_t base; + uint64_t current; + uint64_t end; + uint64_t iterations; + uint64_t remaining; + size_t code_size; + double start; + double finish; + uc_err err; + + base = DATA_ADDRESS; + current = base; + if (definition->conflict_access) { + end = base + CONFLICT_STRIDE * CONFLICT_PAGES; + iterations = loads; + code_size = sizeof(conflict_code); + } else { + end = base + DENSE_SIZE; + iterations = loads / DENSE_LOADS_PER_ITERATION; + code_size = sizeof(dense_code); + } + + err = uc_reg_write(uc, UC_X86_REG_RBX, &base); + if (!report_uc_error("uc_reg_write(RBX)", err)) { + return false; + } + err = uc_reg_write(uc, UC_X86_REG_RSI, ¤t); + if (!report_uc_error("uc_reg_write(RSI)", err)) { + return false; + } + err = uc_reg_write(uc, UC_X86_REG_RDI, &end); + if (!report_uc_error("uc_reg_write(RDI)", err)) { + return false; + } + err = uc_reg_write(uc, UC_X86_REG_RCX, &iterations); + if (!report_uc_error("uc_reg_write(RCX)", err)) { + return false; + } + + start = monotonic_seconds(); + err = uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + code_size, 0, 0); + finish = monotonic_seconds(); + if (!report_uc_error("uc_emu_start", err)) { + return false; + } + err = uc_reg_read(uc, UC_X86_REG_RCX, &remaining); + if (!report_uc_error("uc_reg_read(RCX)", err)) { + return false; + } + if (remaining != 0) { + fprintf(stderr, "%s stopped with RCX=%" PRIu64 "\n", + definition->name, remaining); + return false; + } + + *milliseconds = (finish - start) * 1000.0; + return true; +} + +static int compare_sample_time(const void *left, const void *right) +{ + const RunSample *a = left; + const RunSample *b = right; + + return (a->milliseconds > b->milliseconds) - + (a->milliseconds < b->milliseconds); +} + +static CounterRange counter_range(const RunSample *samples, size_t count, + size_t member_offset) +{ + CounterRange range; + size_t i; + + range.minimum = UINT64_MAX; + range.maximum = 0; + for (i = 0; i < count; i++) { + const uint8_t *counts = (const uint8_t *)&samples[i].counts; + const uint64_t *value = (const uint64_t *)(counts + member_offset); + + if (*value < range.minimum) { + range.minimum = *value; + } + if (*value > range.maximum) { + range.maximum = *value; + } + } + return range; +} + +static void print_summary(const BenchmarkDefinition *definition, + RunSample *samples, size_t count, + uint64_t loads) +{ + CounterRange hook_reads; + CounterRange tlb_fills; + CounterRange read_fills; + CounterRange fetch_fills; + double median; + double minimum; + double maximum; + double rate; + size_t i; + + minimum = samples[0].milliseconds; + maximum = samples[0].milliseconds; + for (i = 1; i < count; i++) { + if (samples[i].milliseconds < minimum) { + minimum = samples[i].milliseconds; + } + if (samples[i].milliseconds > maximum) { + maximum = samples[i].milliseconds; + } + } + hook_reads = counter_range(samples, count, + offsetof(CallbackCounts, hook_reads)); + tlb_fills = counter_range(samples, count, + offsetof(CallbackCounts, tlb_fills)); + read_fills = counter_range(samples, count, + offsetof(CallbackCounts, read_fills)); + fetch_fills = counter_range(samples, count, + offsetof(CallbackCounts, fetch_fills)); + + qsort(samples, count, sizeof(*samples), compare_sample_time); + if ((count & 1) != 0) { + median = samples[count / 2].milliseconds; + } else { + median = (samples[count / 2 - 1].milliseconds + + samples[count / 2].milliseconds) / 2.0; + } + rate = (double)loads / (median * 1000.0); + + printf("%-14s median=%9.3f ms min=%9.3f max=%9.3f " + "rate=%9.3f Mload/s\n", + definition->name, median, minimum, maximum, rate); + printf(" hook_reads=%" PRIu64 "..%" PRIu64 + " tlb_fills=%" PRIu64 "..%" PRIu64 + " read=%" PRIu64 "..%" PRIu64 + " fetch=%" PRIu64 "..%" PRIu64 "\n", + hook_reads.minimum, hook_reads.maximum, tlb_fills.minimum, + tlb_fills.maximum, read_fills.minimum, read_fills.maximum, + fetch_fills.minimum, fetch_fills.maximum); +} + +static bool run_benchmark(const BenchmarkDefinition *definition, + const Options *options) +{ + CallbackCounts counts; + RunSample *samples; + uc_engine *uc; + uc_err err; + size_t i; + bool success; + + if (!definition->conflict_access && + options->loads % DENSE_LOADS_PER_ITERATION != 0) { + fprintf(stderr, + "%s requires --loads to be a multiple of %" PRIu64 "\n", + definition->name, DENSE_LOADS_PER_ITERATION); + return false; + } + if (options->repeats > SIZE_MAX / sizeof(*samples)) { + fprintf(stderr, "--repeats is too large\n"); + return false; + } + + memset(&counts, 0, sizeof(counts)); + uc = NULL; + samples = calloc(options->repeats, sizeof(*samples)); + if (samples == NULL) { + fprintf(stderr, "failed to allocate sample storage\n"); + return false; + } + if (!definition->recreate_engine && + !setup_engine(definition, &counts, &uc)) { + free(samples); + return false; + } + + success = false; + for (i = 0; i < options->warmup; i++) { + memset(&counts, 0, sizeof(counts)); + if (definition->recreate_engine) { + if (uc != NULL) { + uc_close(uc); + uc = NULL; + } + if (!setup_engine(definition, &counts, &uc)) { + goto cleanup; + } + err = UC_ERR_OK; + } else { + err = uc_ctl_flush_tlb(uc); + } + if (!report_uc_error("prepare warmup", err) || + !run_once(uc, definition, options->loads, + &samples[0].milliseconds)) { + goto cleanup; + } + } + + for (i = 0; i < options->repeats; i++) { + memset(&counts, 0, sizeof(counts)); + if (definition->recreate_engine) { + if (uc != NULL) { + uc_close(uc); + uc = NULL; + } + if (!setup_engine(definition, &counts, &uc)) { + goto cleanup; + } + err = UC_ERR_OK; + } else { + err = uc_ctl_flush_tlb(uc); + } + if (!report_uc_error("prepare repeat", err) || + !run_once(uc, definition, options->loads, + &samples[i].milliseconds)) { + goto cleanup; + } + samples[i].counts = counts; + } + + print_summary(definition, samples, options->repeats, options->loads); + success = true; + +cleanup: + if (uc != NULL) { + err = uc_close(uc); + if (!report_uc_error("uc_close", err)) { + success = false; + } + } + free(samples); + return success; +} + +int main(int argc, char **argv) +{ + Options options; + const BenchmarkDefinition *definition; + unsigned int major; + unsigned int minor; + size_t i; + int parse_result; + + parse_result = parse_arguments(argc, argv, &options); + if (parse_result != 0) { + if (parse_result < 0) { + print_usage(argv[0]); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } + + uc_version(&major, &minor); + printf("Unicorn %u.%u, loads=%" PRIu64 ", warmup=%zu, repeats=%zu\n", + major, minor, options.loads, options.warmup, options.repeats); + printf("dense=64 KiB, bounded hook=first 4 KiB, " + "conflicts=16 pages x 1 MiB stride\n"); + + if (strcmp(options.mode, "all") != 0) { + definition = find_benchmark(options.mode); + return run_benchmark(definition, &options) ? + EXIT_SUCCESS : EXIT_FAILURE; + } + + for (i = 0; i < sizeof(benchmark_definitions) / + sizeof(benchmark_definitions[0]); i++) { + if (!run_benchmark(&benchmark_definitions[i], &options)) { + return EXIT_FAILURE; + } + } + return EXIT_SUCCESS; +} diff --git a/tests/fuzz/Makefile b/tests/fuzz/Makefile index c0b6946e0d..1dde066b84 100644 --- a/tests/fuzz/Makefile +++ b/tests/fuzz/Makefile @@ -11,10 +11,22 @@ LDFLAGS += ../../libunicorn.a ALL_TESTS_SOURCES = $(wildcard fuzz*.c) ALL_TESTS = $(ALL_TESTS_SOURCES:%.c=%) +RAW_FUZZ_TESTS = $(filter fuzz_emu_%,$(ALL_TESTS)) .PHONY: all all: ${ALL_TESTS} +.PHONY: replay +replay: all + @set -e; for test in $(RAW_FUZZ_TESTS); do \ + ./$$test corpus/raw; \ + done + @if test -x ./fuzz_uc_api; then ./fuzz_uc_api corpus/fuzz_uc_api; fi + +.PHONY: external-corpus +external-corpus: all + ./dlcorpus.sh . + .PHONY: clean clean: rm -rf ${ALL_TESTS} diff --git a/tests/fuzz/corpus/external-corpora.tsv b/tests/fuzz/corpus/external-corpora.tsv new file mode 100644 index 0000000000..bc40429ddd --- /dev/null +++ b/tests/fuzz/corpus/external-corpora.tsv @@ -0,0 +1,23 @@ +# target sha256 url +fuzz_emu_arm_arm 7c60b7bb6907e670ff12686e1f9d7dc89be248826c16dc32cb597d5a7b85d568 https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_arm_arm/public.zip +fuzz_emu_arm_armbe a4a1b7fbc8776ece467cab816d03ab03f2c067bfe2cd9dcd9f82b361de1e3c7f https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_arm_armbe/public.zip +fuzz_emu_arm_thumb 9be72fef8e91bd4431c4fcaeb8748506e16b49b5d2e6fabbde11044a8152d552 https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_arm_thumb/public.zip +fuzz_emu_arm64_arm b842a9596be46cf8aec331c6d7d3056d1ee67a87fdb75663a7999a64056ff17a https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_arm64_arm/public.zip +fuzz_emu_arm64_armbe 7ea32599d636317e53287dd7a56013a42a15acd575f2a636e9ed04904edbf199 https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_arm64_armbe/public.zip +fuzz_emu_m68k_be c88366d7949afbe679e5cfc34a02165d9132af4e2398e78622cb3e7a1c5d55df https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_m68k_be/public.zip +fuzz_emu_mips_32be 777e7e6c788fcba4e673fbb6e6a1aa995a578b7aae6395ba167cc0f92a0abbbb https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_mips_32be/public.zip +fuzz_emu_mips_32le f09bd198b11898335627ba1db4ad926b5948b2ff93bcacfdcfd5f3d62b50b565 https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_mips_32le/public.zip +fuzz_emu_mips_64be - - +fuzz_emu_mips_64le - - +fuzz_emu_ppc_32be - - +fuzz_emu_ppc_64be - - +fuzz_emu_riscv_32le - - +fuzz_emu_riscv_64le - - +fuzz_emu_s390x_be 73b83433f15c9d0a1cc62af4b91a2de1730bb86da1a119fd29dec17764205645 https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_s390x_be/public.zip +fuzz_emu_sparc_32be 6d2976512a0a852c68b999e42298572499699dc9fda06721de27724dfd82a034 https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_sparc_32be/public.zip +fuzz_emu_sparc_64be - - +fuzz_emu_tricore_le - - +fuzz_emu_x86_16 d7bbaef28b70c04545945ca95986dde6e842ed4f5591db6a9241ffedbd323d6e https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_x86_16/public.zip +fuzz_emu_x86_32 1198bbf040045859f0c31433ecf345f5df11b872867e2db30bbef891ae9540b6 https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_x86_32/public.zip +fuzz_emu_x86_64 ae3b30cb3c2630cb327d3e9f0febb31179a6faa27858229e317b5decb7dba6be https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_fuzz_emu_x86_64/public.zip +fuzz_uc_api - - diff --git a/tests/fuzz/corpus/fuzz_emu.dict b/tests/fuzz/corpus/fuzz_emu.dict new file mode 100644 index 0000000000..186493dec7 --- /dev/null +++ b/tests/fuzz/corpus/fuzz_emu.dict @@ -0,0 +1,7 @@ +nop_x86="\x90" +ret_x86="\xc3" +nop_arm="\x00\xf0\x20\xe3" +nop_thumb="\x00\xbf" +nop_arm64="\x1f\x20\x03\xd5" +nop_mips_be="\x00\x00\x00\x00" +nop_mips_le="\x00\x00\x00\x00" diff --git a/tests/fuzz/corpus/fuzz_uc_api.dict b/tests/fuzz/corpus/fuzz_uc_api.dict new file mode 100644 index 0000000000..d92a66ed5d --- /dev/null +++ b/tests/fuzz/corpus/fuzz_uc_api.dict @@ -0,0 +1,13 @@ +map="\x00\x00\x07" +unmap="\x01\x00\x00" +protect="\x02\x00\x05" +write="\x03\x00\x04\x90\x90\xeb\xfc" +read="\x04\x00\x08" +emulate="\x05\x00\x10" +context_save="\x06\x00\x00" +context_restore="\x07\x00\x00" +hook_add="\x08\x00\x00" +hook_del="\x09\x00\x00" +memory_hook_add="\x0a\x00\x02" +memory_hook_del="\x0b\x00\x00" +regions="\x0c\x00\x00" diff --git a/tests/fuzz/corpus/fuzz_uc_api/lifecycle.seed b/tests/fuzz/corpus/fuzz_uc_api/lifecycle.seed new file mode 100644 index 0000000000..98bc60ccc0 --- /dev/null +++ b/tests/fuzz/corpus/fuzz_uc_api/lifecycle.seed @@ -0,0 +1 @@ +ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 diff --git a/tests/fuzz/corpus/raw/branch.seed b/tests/fuzz/corpus/raw/branch.seed new file mode 100644 index 0000000000..500ffd8c9c --- /dev/null +++ b/tests/fuzz/corpus/raw/branch.seed @@ -0,0 +1 @@ +AAAAAAAABBBB diff --git a/tests/fuzz/corpus/raw/minimal.seed b/tests/fuzz/corpus/raw/minimal.seed new file mode 100644 index 0000000000..56bec9fae2 --- /dev/null +++ b/tests/fuzz/corpus/raw/minimal.seed @@ -0,0 +1 @@ +Unicorn diff --git a/tests/fuzz/dlcorpus.sh b/tests/fuzz/dlcorpus.sh index f159e80cdb..0580e65f23 100644 --- a/tests/fuzz/dlcorpus.sh +++ b/tests/fuzz/dlcorpus.sh @@ -1,11 +1,112 @@ -#/bin/sh -#change to script directory -cd `dirname $0` -ls fuzz_emu*.c | sed 's/.c//' | while read target -do - #download public corpus - wget "https://storage.googleapis.com/unicorn-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/unicorn_$target/public.zip" - unzip -q public.zip -d corpus_$target - #run target on corpus - ./$target corpus_$target +#!/bin/sh +set -eu + +if [ "$#" -lt 1 ] || [ "$#" -gt 3 ]; then + echo "usage: $0 [corpus-directory] [--libfuzzer]" >&2 + exit 1 +fi + +libfuzzer=0 +if [ "$#" -eq 3 ]; then + if [ "$3" != "--libfuzzer" ]; then + echo "usage: $0 [corpus-directory] [--libfuzzer]" >&2 + exit 1 + fi + libfuzzer=1 +fi + +fuzz_runs=${UNICORN_FUZZ_RUNS:-0} +fuzz_timeout=${UNICORN_FUZZ_TIMEOUT:-5} +if [ "$libfuzzer" -eq 1 ]; then + case "$fuzz_runs" in + ''|*[!0-9]*) + echo "UNICORN_FUZZ_RUNS must be a non-negative integer" >&2 + exit 1 + ;; + esac + case "$fuzz_timeout" in + ''|*[!0-9]*|0*) + echo "UNICORN_FUZZ_TIMEOUT must be a positive integer" >&2 + exit 1 + ;; + esac +fi + +source_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd) +binary_dir=$(CDPATH= cd -- "$1" && pwd) +manifest="$source_dir/corpus/external-corpora.tsv" +corpus_dir=${2:-"$binary_dir/corpus"} +case "$corpus_dir" in + /*) ;; + *) corpus_dir="$PWD/$corpus_dir" ;; +esac +mkdir -p "$corpus_dir" + +sha256_file() +{ + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + else + shasum -a 256 "$1" | awk '{print $1}' + fi +} + +download() +{ + if command -v curl >/dev/null 2>&1; then + curl --fail --location --show-error --silent \ + --retry 5 --retry-delay 2 --output "$2" "$1" + else + wget --quiet --tries=5 --output-document="$2" "$1" + fi +} + +run_target() +{ + if [ "$libfuzzer" -eq 1 ]; then + "$1" "-runs=$fuzz_runs" "-timeout=$fuzz_timeout" "$2" + else + "$1" "$2" + fi +} + +while read -r target expected_hash url; do + case "$target" in + ''|'#'*) continue ;; + esac + + if [ ! -x "$binary_dir/$target" ]; then + continue + fi + + if [ "$expected_hash" = "-" ] || [ "$url" = "-" ]; then + echo "$target: no external corpus is published; using local seeds" + continue + fi + + archive=$(mktemp "${TMPDIR:-/tmp}/unicorn-corpus.XXXXXX") + download "$url" "$archive" + actual_hash=$(sha256_file "$archive") + if [ "$actual_hash" != "$expected_hash" ]; then + rm -f "$archive" + echo "$target: corpus SHA-256 mismatch" >&2 + exit 1 + fi + + target_corpus="$corpus_dir/$target" + rm -rf "$target_corpus" + mkdir -p "$target_corpus" + unzip -qo "$archive" -d "$target_corpus" + rm -f "$archive" + run_target "$binary_dir/$target" "$target_corpus" +done < "$manifest" + +for binary in "$binary_dir"/fuzz_emu_*; do + if [ -x "$binary" ]; then + run_target "$binary" "$source_dir/corpus/raw" + fi done + +if [ -x "$binary_dir/fuzz_uc_api" ]; then + run_target "$binary_dir/fuzz_uc_api" "$source_dir/corpus/fuzz_uc_api" +fi diff --git a/tests/fuzz/fuzz_emu.h b/tests/fuzz/fuzz_emu.h new file mode 100644 index 0000000000..5d0b079ade --- /dev/null +++ b/tests/fuzz/fuzz_emu.h @@ -0,0 +1,51 @@ +#ifndef UNICORN_TESTS_FUZZ_EMU_H +#define UNICORN_TESTS_FUZZ_EMU_H + +#include +#include +#include + +#include + +#ifndef UC_FUZZ_ARCH +#error UC_FUZZ_ARCH must be defined +#endif + +#ifndef UC_FUZZ_MODE +#error UC_FUZZ_MODE must be defined +#endif + +#define FUZZ_CODE_ADDRESS UINT64_C(0x1000000) +#define FUZZ_MAP_SIZE (4 * 1024 * 1024) +#define FUZZ_MAX_INPUT_SIZE 4096 +#define FUZZ_MAX_INSTRUCTIONS 4096 + +static void fuzz_require_ok(uc_err err) +{ + if (err != UC_ERR_OK) { + abort(); + } +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + uc_engine *uc = NULL; + + if (size > FUZZ_MAX_INPUT_SIZE) { + return 0; + } + + fuzz_require_ok(uc_open(UC_FUZZ_ARCH, UC_FUZZ_MODE, &uc)); + fuzz_require_ok( + uc_mem_map(uc, FUZZ_CODE_ADDRESS, FUZZ_MAP_SIZE, UC_PROT_ALL)); + if (size != 0) { + fuzz_require_ok(uc_mem_write(uc, FUZZ_CODE_ADDRESS, data, size)); + } + + (void)uc_emu_start(uc, FUZZ_CODE_ADDRESS, FUZZ_CODE_ADDRESS + size, 0, + FUZZ_MAX_INSTRUCTIONS); + fuzz_require_ok(uc_close(uc)); + return 0; +} + +#endif diff --git a/tests/fuzz/fuzz_emu.options b/tests/fuzz/fuzz_emu.options index 9fda93fcb3..685cf82d92 100644 --- a/tests/fuzz/fuzz_emu.options +++ b/tests/fuzz/fuzz_emu.options @@ -1,2 +1,3 @@ [libfuzzer] max_len = 4096 +timeout = 5 diff --git a/tests/fuzz/fuzz_emu_arm64_arm.c b/tests/fuzz/fuzz_emu_arm64_arm.c index 9e694e8d50..0eae25d4bc 100644 --- a/tests/fuzz/fuzz_emu_arm64_arm.c +++ b/tests/fuzz/fuzz_emu_arm64_arm.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_ARM64 +#define UC_FUZZ_MODE UC_MODE_ARM +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_arm64_armbe.c b/tests/fuzz/fuzz_emu_arm64_armbe.c index 716bf861bb..953497d7b3 100644 --- a/tests/fuzz/fuzz_emu_arm64_armbe.c +++ b/tests/fuzz/fuzz_emu_arm64_armbe.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_ARM64, UC_MODE_ARM + UC_MODE_BIG_ENDIAN, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_ARM64 +#define UC_FUZZ_MODE UC_MODE_ARM | UC_MODE_BIG_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_arm_arm.c b/tests/fuzz/fuzz_emu_arm_arm.c index ca39317531..b344633cca 100644 --- a/tests/fuzz/fuzz_emu_arm_arm.c +++ b/tests/fuzz/fuzz_emu_arm_arm.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_ARM +#define UC_FUZZ_MODE UC_MODE_ARM +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_arm_armbe.c b/tests/fuzz/fuzz_emu_arm_armbe.c index aadd385894..65c98f7590 100644 --- a/tests/fuzz/fuzz_emu_arm_armbe.c +++ b/tests/fuzz/fuzz_emu_arm_armbe.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_ARM, UC_MODE_ARM + UC_MODE_BIG_ENDIAN, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_ARM +#define UC_FUZZ_MODE UC_MODE_ARM | UC_MODE_BIG_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_arm_thumb.c b/tests/fuzz/fuzz_emu_arm_thumb.c index d50dd6a261..9f25ad8eca 100644 --- a/tests/fuzz/fuzz_emu_arm_thumb.c +++ b/tests/fuzz/fuzz_emu_arm_thumb.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_ARM +#define UC_FUZZ_MODE UC_MODE_THUMB +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_m68k_be.c b/tests/fuzz/fuzz_emu_m68k_be.c index cd2bf597bc..411771c538 100644 --- a/tests/fuzz/fuzz_emu_m68k_be.c +++ b/tests/fuzz/fuzz_emu_m68k_be.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_M68K +#define UC_FUZZ_MODE UC_MODE_BIG_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_mips_32be.c b/tests/fuzz/fuzz_emu_mips_32be.c index dbe3141118..4bf9449789 100644 --- a/tests/fuzz/fuzz_emu_mips_32be.c +++ b/tests/fuzz/fuzz_emu_mips_32be.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 + UC_MODE_BIG_ENDIAN, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_MIPS +#define UC_FUZZ_MODE UC_MODE_MIPS32 | UC_MODE_BIG_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_mips_32le.c b/tests/fuzz/fuzz_emu_mips_32le.c index 02786ce392..83f15a7c88 100644 --- a/tests/fuzz/fuzz_emu_mips_32le.c +++ b/tests/fuzz/fuzz_emu_mips_32le.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 + UC_MODE_LITTLE_ENDIAN, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_MIPS +#define UC_FUZZ_MODE UC_MODE_MIPS32 | UC_MODE_LITTLE_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_mips_64be.c b/tests/fuzz/fuzz_emu_mips_64be.c new file mode 100644 index 0000000000..0ca02ed739 --- /dev/null +++ b/tests/fuzz/fuzz_emu_mips_64be.c @@ -0,0 +1,3 @@ +#define UC_FUZZ_ARCH UC_ARCH_MIPS +#define UC_FUZZ_MODE UC_MODE_MIPS64 | UC_MODE_BIG_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_mips_64le.c b/tests/fuzz/fuzz_emu_mips_64le.c new file mode 100644 index 0000000000..3a6bd5ee85 --- /dev/null +++ b/tests/fuzz/fuzz_emu_mips_64le.c @@ -0,0 +1,3 @@ +#define UC_FUZZ_ARCH UC_ARCH_MIPS +#define UC_FUZZ_MODE UC_MODE_MIPS64 | UC_MODE_LITTLE_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_ppc_32be.c b/tests/fuzz/fuzz_emu_ppc_32be.c new file mode 100644 index 0000000000..149d2ab8d6 --- /dev/null +++ b/tests/fuzz/fuzz_emu_ppc_32be.c @@ -0,0 +1,3 @@ +#define UC_FUZZ_ARCH UC_ARCH_PPC +#define UC_FUZZ_MODE UC_MODE_32 | UC_MODE_BIG_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_ppc_64be.c b/tests/fuzz/fuzz_emu_ppc_64be.c new file mode 100644 index 0000000000..fee86c7e30 --- /dev/null +++ b/tests/fuzz/fuzz_emu_ppc_64be.c @@ -0,0 +1,3 @@ +#define UC_FUZZ_ARCH UC_ARCH_PPC +#define UC_FUZZ_MODE UC_MODE_64 | UC_MODE_BIG_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_riscv_32le.c b/tests/fuzz/fuzz_emu_riscv_32le.c new file mode 100644 index 0000000000..b6fcc62658 --- /dev/null +++ b/tests/fuzz/fuzz_emu_riscv_32le.c @@ -0,0 +1,3 @@ +#define UC_FUZZ_ARCH UC_ARCH_RISCV +#define UC_FUZZ_MODE UC_MODE_RISCV32 +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_riscv_64le.c b/tests/fuzz/fuzz_emu_riscv_64le.c new file mode 100644 index 0000000000..ac036708d2 --- /dev/null +++ b/tests/fuzz/fuzz_emu_riscv_64le.c @@ -0,0 +1,3 @@ +#define UC_FUZZ_ARCH UC_ARCH_RISCV +#define UC_FUZZ_MODE UC_MODE_RISCV64 +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_s390x_be.c b/tests/fuzz/fuzz_emu_s390x_be.c index 88d4873adf..9edc9b5000 100644 --- a/tests/fuzz/fuzz_emu_s390x_be.c +++ b/tests/fuzz/fuzz_emu_s390x_be.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_S390X, UC_MODE_BIG_ENDIAN, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_S390X +#define UC_FUZZ_MODE UC_MODE_BIG_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_sparc_32be.c b/tests/fuzz/fuzz_emu_sparc_32be.c index 8d3209c086..1511b33ecd 100644 --- a/tests/fuzz/fuzz_emu_sparc_32be.c +++ b/tests/fuzz/fuzz_emu_sparc_32be.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_SPARC, UC_MODE_SPARC32|UC_MODE_BIG_ENDIAN, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_SPARC +#define UC_FUZZ_MODE UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_sparc_64be.c b/tests/fuzz/fuzz_emu_sparc_64be.c new file mode 100644 index 0000000000..c926326358 --- /dev/null +++ b/tests/fuzz/fuzz_emu_sparc_64be.c @@ -0,0 +1,3 @@ +#define UC_FUZZ_ARCH UC_ARCH_SPARC +#define UC_FUZZ_MODE UC_MODE_SPARC64 | UC_MODE_BIG_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_tricore_le.c b/tests/fuzz/fuzz_emu_tricore_le.c new file mode 100644 index 0000000000..e85b5477d5 --- /dev/null +++ b/tests/fuzz/fuzz_emu_tricore_le.c @@ -0,0 +1,3 @@ +#define UC_FUZZ_ARCH UC_ARCH_TRICORE +#define UC_FUZZ_MODE UC_MODE_LITTLE_ENDIAN +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_x86_16.c b/tests/fuzz/fuzz_emu_x86_16.c index 3e5c6c47b7..18e42d7b23 100644 --- a/tests/fuzz/fuzz_emu_x86_16.c +++ b/tests/fuzz/fuzz_emu_x86_16.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_X86, UC_MODE_16, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_X86 +#define UC_FUZZ_MODE UC_MODE_16 +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_x86_32.c b/tests/fuzz/fuzz_emu_x86_32.c index d1e330536f..a5d8c7b701 100644 --- a/tests/fuzz/fuzz_emu_x86_32.c +++ b/tests/fuzz/fuzz_emu_x86_32.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_X86 +#define UC_FUZZ_MODE UC_MODE_32 +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_emu_x86_64.c b/tests/fuzz/fuzz_emu_x86_64.c index f4f63cb531..7d2431fae7 100644 --- a/tests/fuzz/fuzz_emu_x86_64.c +++ b/tests/fuzz/fuzz_emu_x86_64.c @@ -1,56 +1,3 @@ -#include - - -// memory address where emulation starts -#define ADDRESS 0x1000000 - -uc_engine *uc; -int initialized = 0; -FILE * outfile = NULL; - - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - uc_err err; - - if (initialized == 0) { - if (outfile == NULL) { - // we compute the output - outfile = fopen("/dev/null", "w"); - if (outfile == NULL) { - printf("failed opening /dev/null\n"); - abort(); - return 0; - } - } - - initialized = 1; - } - - // Not global as we must reset this structure - // Initialize emulator in supplied mode - err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - abort(); - } - - // map 4MB memory for this emulation - uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, Data, Size)) { - printf("Failed to write emulation code to memory, quit!\n"); - abort(); - } - - // emulate code in infinite time & 4096 instructions - // avoid timeouts with infinite loops - err=uc_emu_start(uc, ADDRESS, ADDRESS + Size, 0, 0x1000); - if (err) { - fprintf(outfile, "Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - } - - uc_close(uc); - - return 0; -} +#define UC_FUZZ_ARCH UC_ARCH_X86 +#define UC_FUZZ_MODE UC_MODE_64 +#include "fuzz_emu.h" diff --git a/tests/fuzz/fuzz_uc_api.c b/tests/fuzz/fuzz_uc_api.c new file mode 100644 index 0000000000..ddbc475a06 --- /dev/null +++ b/tests/fuzz/fuzz_uc_api.c @@ -0,0 +1,374 @@ +#include +#include +#include +#include + +#include + +#define API_FUZZ_BASE UINT64_C(0x1000000) +#define API_FUZZ_RECOVERY UINT64_C(0x2000000) +#define API_FUZZ_PAGE_SIZE 0x1000 +#define API_FUZZ_PAGE_COUNT 8 +#define API_FUZZ_MAX_INPUT_SIZE 4096 +#define API_FUZZ_MAX_OPERATIONS 256 +#define API_FUZZ_MAX_EMULATION_STEPS 64 +#define API_FUZZ_MAX_X86_INSN_SIZE 15 + +typedef struct ApiFuzzInput { + const uint8_t *data; + size_t size; + size_t offset; +} ApiFuzzInput; + +typedef struct ApiFuzzState { + uc_context *context; + uint64_t nested_address; + uint8_t code_action; + uint8_t memory_action; + size_t instruction_budget; + bool context_valid; + bool nested_active; +} ApiFuzzState; + +typedef struct ApiFuzzNestedCheck { + ApiFuzzState *state; + uint64_t outer_address; + uint64_t inner_address; + unsigned int nested_count; +} ApiFuzzNestedCheck; + +static uint8_t fuzz_read_u8(ApiFuzzInput *input) +{ + if (input->offset == input->size) { + return 0; + } + return input->data[input->offset++]; +} + +static uint64_t fuzz_page(uint8_t index) +{ + return API_FUZZ_BASE + + (uint64_t)(index % API_FUZZ_PAGE_COUNT) * API_FUZZ_PAGE_SIZE; +} + +static uint32_t fuzz_permissions(uint8_t value) +{ + return value & UC_PROT_ALL; +} + +static bool fuzz_is_x86_string_opcode(uint8_t opcode) +{ + return (opcode >= 0x6c && opcode <= 0x6f) || + (opcode >= 0xa4 && opcode <= 0xa7) || + (opcode >= 0xaa && opcode <= 0xaf); +} + +static bool fuzz_is_rep_string(uc_engine *uc, uint64_t address, uint32_t size) +{ + uint8_t code[API_FUZZ_MAX_X86_INSN_SIZE]; + bool has_rep = false; + unsigned int i; + + if (size == 0 || size > sizeof(code) || + uc_mem_read(uc, address, code, size) != UC_ERR_OK) { + return false; + } + + for (i = 0; i < size; i++) { + switch (code[i]) { + case 0xf2: + case 0xf3: + has_rep = true; + continue; + case 0x26: + case 0x2e: + case 0x36: + case 0x3e: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + continue; + default: + if (code[i] >= 0x40 && code[i] <= 0x4f) { + continue; + } + return has_rep && fuzz_is_x86_string_opcode(code[i]); + } + } + return false; +} + +static void fuzz_budget_hook(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + ApiFuzzState *state = user_data; + uint64_t repeat_count; + + if (state->instruction_budget == 0) { + (void)uc_emu_stop(uc); + return; + } + state->instruction_budget--; + + if (fuzz_is_rep_string(uc, address, size) && + uc_reg_read(uc, UC_X86_REG_RCX, &repeat_count) == UC_ERR_OK && + repeat_count > API_FUZZ_MAX_EMULATION_STEPS) { + repeat_count = API_FUZZ_MAX_EMULATION_STEPS; + (void)uc_reg_write(uc, UC_X86_REG_RCX, &repeat_count); + } +} + +static uc_err fuzz_emu_start(uc_engine *uc, ApiFuzzState *state, + uint64_t begin, uint64_t until, size_t count) +{ + size_t previous_budget = state->instruction_budget; + uc_err err; + + state->instruction_budget = count; + err = uc_emu_start(uc, begin, until, 0, count); + state->instruction_budget = previous_budget; + return err; +} + +static void fuzz_code_hook(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + static const uint8_t smc_code[] = {0x90, 0x90, 0xeb, 0xfc}; + ApiFuzzState *state = user_data; + + switch (state->code_action % 5) { + case 0: + (void)uc_emu_stop(uc); + break; + case 1: + if (!state->nested_active) { + state->nested_active = true; + (void)fuzz_emu_start(uc, state, state->nested_address, + state->nested_address + 2, 2); + state->nested_active = false; + } + break; + case 2: + (void)uc_ctl(uc, UC_CTL_TB_FLUSH); + break; + case 3: + if (state->context_valid) { + (void)uc_context_restore(uc, state->context); + } + break; + case 4: + (void)uc_mem_write(uc, address, smc_code, + size < sizeof(smc_code) ? size : sizeof(smc_code)); + break; + } +} + +static void fuzz_memory_hook(uc_engine *uc, uc_mem_type type, uint64_t address, + int size, int64_t value, void *user_data) +{ + ApiFuzzState *state = user_data; + uint64_t page = address & ~(uint64_t)(API_FUZZ_PAGE_SIZE - 1); + + switch (state->memory_action % 3) { + case 0: + (void)uc_mem_unmap(uc, page, API_FUZZ_PAGE_SIZE); + break; + case 1: + (void)uc_mem_protect(uc, page, API_FUZZ_PAGE_SIZE, UC_PROT_READ); + break; + case 2: + (void)uc_emu_stop(uc); + break; + } +} + +static void fuzz_require_ok(uc_err err) +{ + if (err != UC_ERR_OK) { + abort(); + } +} + +static void fuzz_nested_budget_hook(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + ApiFuzzNestedCheck *check = user_data; + + if (address != check->outer_address || check->nested_count != 0 || + size != 3 || check->state->instruction_budget != 1) { + abort(); + } + check->nested_count++; + fuzz_require_ok(fuzz_emu_start(uc, check->state, check->inner_address, + check->inner_address + 3, 1)); + if (check->state->instruction_budget != 1) { + abort(); + } +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + static const uint8_t recovery_code[] = {0x90, 0x90}; + static const uint8_t outer_code[] = {0x48, 0xff, 0xc0, 0x48, 0xff, 0xc0}; + static const uint8_t inner_code[] = {0x48, 0xff, 0xc3}; + uint8_t read_buffer[32]; + ApiFuzzInput input = {data, size, 0}; + ApiFuzzState state = {0}; + ApiFuzzNestedCheck nested_check = { + &state, API_FUZZ_RECOVERY, API_FUZZ_RECOVERY + 0x100, 0, + }; + uc_hook code_hook = 0; + uc_hook budget_hook = 0; + uc_hook memory_hook = 0; + uc_hook nested_hook = 0; + uc_engine *uc = NULL; + uc_context *initial_context = NULL; + unsigned int operation; + uint64_t rax = 0; + uint64_t rbx = 0; + uint64_t rip; + + if (size > API_FUZZ_MAX_INPUT_SIZE) { + return 0; + } + + fuzz_require_ok(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + fuzz_require_ok( + uc_mem_map(uc, API_FUZZ_RECOVERY, API_FUZZ_PAGE_SIZE, UC_PROT_ALL)); + fuzz_require_ok(uc_mem_write(uc, API_FUZZ_RECOVERY, recovery_code, + sizeof(recovery_code))); + fuzz_require_ok(uc_context_alloc(uc, &initial_context)); + fuzz_require_ok(uc_context_save(uc, initial_context)); + fuzz_require_ok(uc_context_alloc(uc, &state.context)); + fuzz_require_ok(uc_context_save(uc, state.context)); + state.context_valid = true; + state.nested_address = API_FUZZ_RECOVERY; + fuzz_require_ok(uc_hook_add(uc, &budget_hook, UC_HOOK_CODE, + fuzz_budget_hook, &state, 1, 0)); + + for (operation = 0; + input.offset < input.size && operation < API_FUZZ_MAX_OPERATIONS; + operation++) { + uint8_t command = fuzz_read_u8(&input); + uint64_t page = fuzz_page(fuzz_read_u8(&input)); + uint8_t argument = fuzz_read_u8(&input); + + switch (command % 13) { + case 0: + (void)uc_mem_map(uc, page, API_FUZZ_PAGE_SIZE, + fuzz_permissions(argument)); + break; + case 1: + (void)uc_mem_unmap(uc, page, API_FUZZ_PAGE_SIZE); + break; + case 2: + (void)uc_mem_protect(uc, page, API_FUZZ_PAGE_SIZE, + fuzz_permissions(argument)); + break; + case 3: { + size_t remaining = input.size - input.offset; + size_t write_size = argument % (sizeof(read_buffer) + 1); + + if (write_size > remaining) { + write_size = remaining; + } + (void)uc_mem_write(uc, page, input.data + input.offset, write_size); + input.offset += write_size; + break; + } + case 4: + (void)uc_mem_read(uc, page, read_buffer, + argument % (sizeof(read_buffer) + 1)); + break; + case 5: + (void)fuzz_emu_start(uc, &state, page, + page + API_FUZZ_PAGE_SIZE, + 1 + argument % API_FUZZ_MAX_EMULATION_STEPS); + break; + case 6: + if (uc_context_save(uc, state.context) == UC_ERR_OK) { + state.context_valid = true; + } + break; + case 7: + if (state.context_valid) { + (void)uc_context_restore(uc, state.context); + } + break; + case 8: + state.code_action = argument; + if (code_hook == 0) { + (void)uc_hook_add(uc, &code_hook, UC_HOOK_CODE, fuzz_code_hook, + &state, API_FUZZ_BASE, API_FUZZ_RECOVERY - 1); + } + break; + case 9: + if (code_hook != 0) { + (void)uc_hook_del(uc, code_hook); + code_hook = 0; + } + break; + case 10: + state.memory_action = argument; + if (memory_hook == 0) { + (void)uc_hook_add(uc, &memory_hook, + UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, + fuzz_memory_hook, &state, API_FUZZ_BASE, + API_FUZZ_RECOVERY - 1); + } + break; + case 11: + if (memory_hook != 0) { + (void)uc_hook_del(uc, memory_hook); + memory_hook = 0; + } + break; + case 12: { + uc_mem_region *regions = NULL; + uint32_t count = 0; + + if (uc_mem_regions(uc, ®ions, &count) == UC_ERR_OK) { + uc_free(regions); + } + break; + } + } + } + + if (code_hook != 0) { + fuzz_require_ok(uc_hook_del(uc, code_hook)); + } + if (memory_hook != 0) { + fuzz_require_ok(uc_hook_del(uc, memory_hook)); + } + fuzz_require_ok(uc_context_restore(uc, initial_context)); + fuzz_require_ok( + uc_mem_protect(uc, API_FUZZ_RECOVERY, API_FUZZ_PAGE_SIZE, UC_PROT_ALL)); + fuzz_require_ok(uc_mem_write(uc, nested_check.outer_address, outer_code, + sizeof(outer_code))); + fuzz_require_ok(uc_mem_write(uc, nested_check.inner_address, inner_code, + sizeof(inner_code))); + fuzz_require_ok(uc_reg_write(uc, UC_X86_REG_RAX, &rax)); + fuzz_require_ok(uc_reg_write(uc, UC_X86_REG_RBX, &rbx)); + fuzz_require_ok(uc_hook_add(uc, &nested_hook, UC_HOOK_CODE, + fuzz_nested_budget_hook, &nested_check, + nested_check.outer_address, + nested_check.outer_address)); + fuzz_require_ok(fuzz_emu_start( + uc, &state, nested_check.outer_address, + nested_check.outer_address + sizeof(outer_code), 2)); + fuzz_require_ok(uc_reg_read(uc, UC_X86_REG_RAX, &rax)); + fuzz_require_ok(uc_reg_read(uc, UC_X86_REG_RBX, &rbx)); + fuzz_require_ok(uc_reg_read(uc, UC_X86_REG_RIP, &rip)); + if (nested_check.nested_count != 1 || rax != 2 || rbx != 1 || + rip != nested_check.outer_address + sizeof(outer_code)) { + abort(); + } + fuzz_require_ok(uc_hook_del(uc, nested_hook)); + fuzz_require_ok(uc_hook_del(uc, budget_hook)); + fuzz_require_ok(uc_context_free(state.context)); + fuzz_require_ok(uc_context_free(initial_context)); + fuzz_require_ok(uc_close(uc)); + return 0; +} diff --git a/tests/fuzz/gentargets.sh b/tests/fuzz/gentargets.sh index ac53ace813..ffc02b75b5 100644 --- a/tests/fuzz/gentargets.sh +++ b/tests/fuzz/gentargets.sh @@ -1,23 +1,34 @@ -#/bin/sh -# generates all fuzz targets for different architectures from the template in fuzz_emu_x86_32.c +#!/bin/sh +set -eu -sed 's/UC_MODE_32/UC_MODE_64/' fuzz_emu_x86_32.c > fuzz_emu_x86_64.c -sed 's/UC_MODE_32/UC_MODE_16/' fuzz_emu_x86_32.c > fuzz_emu_x86_16.c +generate() +{ + arch=$1 + mode=$2 + output=$3 -sed 's/UC_ARCH_X86/UC_ARCH_SPARC/' fuzz_emu_x86_32.c | sed 's/UC_MODE_32/UC_MODE_SPARC32|UC_MODE_BIG_ENDIAN/' > fuzz_emu_sparc_32be.c -#sed 's/UC_ARCH_X86/UC_ARCH_SPARC/' fuzz_emu_x86_32.c | sed 's/UC_MODE_32/UC_MODE_SPARC64|UC_MODE_BIG_ENDIAN/' > fuzz_emu_sparc_64be.c + sed -e "s/^#define UC_FUZZ_ARCH .*/#define UC_FUZZ_ARCH ${arch}/" \ + -e "s/^#define UC_FUZZ_MODE .*/#define UC_FUZZ_MODE ${mode}/" \ + fuzz_emu_x86_32.c > "${output}" +} -sed 's/UC_ARCH_X86/UC_ARCH_M68K/' fuzz_emu_x86_32.c | sed 's/UC_MODE_32/UC_MODE_BIG_ENDIAN/' > fuzz_emu_m68k_be.c - -sed 's/UC_ARCH_X86/UC_ARCH_MIPS/' fuzz_emu_x86_32.c | sed 's/UC_MODE_32/UC_MODE_MIPS32 + UC_MODE_LITTLE_ENDIAN/' > fuzz_emu_mips_32le.c -sed 's/UC_ARCH_X86/UC_ARCH_MIPS/' fuzz_emu_x86_32.c | sed 's/UC_MODE_32/UC_MODE_MIPS32 + UC_MODE_BIG_ENDIAN/' > fuzz_emu_mips_32be.c - -sed 's/UC_ARCH_X86/UC_ARCH_ARM64/' fuzz_emu_x86_32.c | sed 's/UC_MODE_32/UC_MODE_ARM/' > fuzz_emu_arm64_arm.c -sed 's/UC_ARCH_X86/UC_ARCH_ARM64/' fuzz_emu_x86_32.c | sed 's/UC_MODE_32/UC_MODE_ARM + UC_MODE_BIG_ENDIAN/' > fuzz_emu_arm64_armbe.c - -sed 's/UC_ARCH_X86/UC_ARCH_ARM/' fuzz_emu_x86_32.c | sed 's/UC_MODE_32/UC_MODE_ARM/' > fuzz_emu_arm_arm.c -sed 's/UC_ARCH_X86/UC_ARCH_ARM/' fuzz_emu_x86_32.c | sed 's/UC_MODE_32/UC_MODE_THUMB/' > fuzz_emu_arm_thumb.c -sed 's/UC_ARCH_X86/UC_ARCH_ARM/' fuzz_emu_x86_32.c | sed 's/UC_MODE_32/UC_MODE_ARM + UC_MODE_BIG_ENDIAN/' > fuzz_emu_arm_armbe.c -#sed 's/UC_ARCH_X86/UC_ARCH_ARM/' fuzz_emu_x86_32.c | sed 's/UC_MODE_32/UC_MODE_THUMB + UC_MODE_BIG_ENDIAN/' > fuzz_emu_arm_thumbbe.c - -sed 's/UC_ARCH_X86/UC_ARCH_S390X/' fuzz_emu_x86_32.c | sed 's/UC_MODE_32/UC_MODE_BIG_ENDIAN/' > fuzz_emu_s390x_be.c \ No newline at end of file +generate UC_ARCH_X86 UC_MODE_16 fuzz_emu_x86_16.c +generate UC_ARCH_X86 UC_MODE_64 fuzz_emu_x86_64.c +generate UC_ARCH_ARM UC_MODE_ARM fuzz_emu_arm_arm.c +generate UC_ARCH_ARM 'UC_MODE_ARM | UC_MODE_BIG_ENDIAN' fuzz_emu_arm_armbe.c +generate UC_ARCH_ARM UC_MODE_THUMB fuzz_emu_arm_thumb.c +generate UC_ARCH_ARM64 UC_MODE_ARM fuzz_emu_arm64_arm.c +generate UC_ARCH_ARM64 'UC_MODE_ARM | UC_MODE_BIG_ENDIAN' fuzz_emu_arm64_armbe.c +generate UC_ARCH_M68K UC_MODE_BIG_ENDIAN fuzz_emu_m68k_be.c +generate UC_ARCH_MIPS 'UC_MODE_MIPS32 | UC_MODE_BIG_ENDIAN' fuzz_emu_mips_32be.c +generate UC_ARCH_MIPS 'UC_MODE_MIPS32 | UC_MODE_LITTLE_ENDIAN' fuzz_emu_mips_32le.c +generate UC_ARCH_MIPS 'UC_MODE_MIPS64 | UC_MODE_BIG_ENDIAN' fuzz_emu_mips_64be.c +generate UC_ARCH_MIPS 'UC_MODE_MIPS64 | UC_MODE_LITTLE_ENDIAN' fuzz_emu_mips_64le.c +generate UC_ARCH_PPC 'UC_MODE_32 | UC_MODE_BIG_ENDIAN' fuzz_emu_ppc_32be.c +generate UC_ARCH_PPC 'UC_MODE_64 | UC_MODE_BIG_ENDIAN' fuzz_emu_ppc_64be.c +generate UC_ARCH_RISCV UC_MODE_RISCV32 fuzz_emu_riscv_32le.c +generate UC_ARCH_RISCV UC_MODE_RISCV64 fuzz_emu_riscv_64le.c +generate UC_ARCH_SPARC 'UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN' fuzz_emu_sparc_32be.c +generate UC_ARCH_SPARC 'UC_MODE_SPARC64 | UC_MODE_BIG_ENDIAN' fuzz_emu_sparc_64be.c +generate UC_ARCH_TRICORE UC_MODE_LITTLE_ENDIAN fuzz_emu_tricore_le.c +generate UC_ARCH_S390X UC_MODE_BIG_ENDIAN fuzz_emu_s390x_be.c diff --git a/tests/fuzz/onedir.c b/tests/fuzz/onedir.c index e0336c81df..1b877792e5 100644 --- a/tests/fuzz/onedir.c +++ b/tests/fuzz/onedir.c @@ -1,79 +1,155 @@ #include -#include #include +#include + +#ifdef _WIN32 +#include +#else #include -#include +#include +#endif + +#define MAX_INPUT_SIZE 4096 +#define MAX_PATH_SIZE 4096 +#define RUN_FILE_SKIPPED 1 -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size); +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); -int main(int argc, char** argv) +static int run_file(const char *path) { - FILE * fp; - uint8_t Data[0x1000]; - size_t Size; - DIR *d; - struct dirent *dir; - int r = 0; + uint8_t data[MAX_INPUT_SIZE]; + long file_size; + FILE *file; - if (argc != 2) { - return 1; + file = fopen(path, "rb"); + if (file == NULL) { + return 3; + } + if (fseek(file, 0, SEEK_END) != 0) { + fclose(file); + return 4; + } + file_size = ftell(file); + if (file_size < 0) { + fclose(file); + return 5; + } + if (file_size > MAX_INPUT_SIZE) { + fclose(file); + return RUN_FILE_SKIPPED; + } + if (fseek(file, 0, SEEK_SET) != 0) { + fclose(file); + return 6; + } + if (file_size != 0 && + fread(data, 1, (size_t)file_size, file) != (size_t)file_size) { + fclose(file); + return 7; } + fclose(file); + LLVMFuzzerTestOneInput(data, (size_t)file_size); + return 0; +} + +#ifdef _WIN32 +static int run_directory(const char *directory, unsigned int *file_count) +{ + WIN32_FIND_DATAA entry; + char pattern[MAX_PATH_SIZE]; + char path[MAX_PATH_SIZE]; + HANDLE find; + int result = 0; + int length; - d = opendir(argv[1]); - if (d == NULL) { - printf("Invalid directory\n"); + length = snprintf(pattern, sizeof(pattern), "%s\\*", directory); + if (length < 0 || (size_t)length >= sizeof(pattern)) { return 2; } - if (chdir(argv[1]) != 0) { - closedir(d); - printf("Invalid directory\n"); + find = FindFirstFileA(pattern, &entry); + if (find == INVALID_HANDLE_VALUE) { return 2; } - - printf("Starting directory %s\n", argv[1]); - while((dir = readdir(d)) != NULL) { - //opens the file, get its size, and reads it into a buffer - if (dir->d_type != DT_REG) { + do { + if ((entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { continue; } - //printf("Running file %s\n", dir->d_name); - fflush(stdout); - fp = fopen(dir->d_name, "rb"); - if (fp == NULL) { - r = 3; + length = + snprintf(path, sizeof(path), "%s\\%s", directory, entry.cFileName); + if (length < 0 || (size_t)length >= sizeof(path)) { + result = 2; break; } - if (fseek(fp, 0L, SEEK_END) != 0) { - fclose(fp); - r = 4; - break; + result = run_file(path); + if (result == RUN_FILE_SKIPPED) { + result = 0; + continue; } - Size = ftell(fp); - if (Size == (size_t) -1) { - fclose(fp); - r = 5; + if (result != 0) { break; - } else if (Size > 0x1000) { - fclose(fp); + } + (*file_count)++; + } while (FindNextFileA(find, &entry)); + if (result == 0 && GetLastError() != ERROR_NO_MORE_FILES) { + result = 2; + } + FindClose(find); + return result; +} +#else +static int run_directory(const char *directory, unsigned int *file_count) +{ + char path[MAX_PATH_SIZE]; + struct dirent *entry; + struct stat status; + DIR *dir; + int result = 0; + int length; + + dir = opendir(directory); + if (dir == NULL) { + return 2; + } + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) { continue; } - if (fseek(fp, 0L, SEEK_SET) != 0) { - fclose(fp); - r = 7; + length = + snprintf(path, sizeof(path), "%s/%s", directory, entry->d_name); + if (length < 0 || (size_t)length >= sizeof(path)) { + result = 2; break; } - if (fread(Data, Size, 1, fp) != 1) { - fclose(fp); - r = 8; + if (stat(path, &status) != 0 || !S_ISREG(status.st_mode)) { + continue; + } + result = run_file(path); + if (result == RUN_FILE_SKIPPED) { + result = 0; + continue; + } + if (result != 0) { break; } - - //lauch fuzzer - LLVMFuzzerTestOneInput(Data, Size); - fclose(fp); + (*file_count)++; } - closedir(d); - printf("Ok : whole directory finished %s\n", argv[1]); - return r; + closedir(dir); + return result; } +#endif + +int main(int argc, char **argv) +{ + unsigned int file_count = 0; + int result; + if (argc != 2) { + return 1; + } + result = run_directory(argv[1], &file_count); + if (result != 0) { + return result; + } + return file_count == 0 ? 8 : 0; +} diff --git a/tests/fuzz/onefile.c b/tests/fuzz/onefile.c index 3146cc0702..1130c066f4 100644 --- a/tests/fuzz/onefile.c +++ b/tests/fuzz/onefile.c @@ -1,49 +1,56 @@ +#include #include #include -#include + +#define MAX_INPUT_SIZE 4096 int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size); -int main(int argc, char** argv) +int main(int argc, char **argv) { - FILE * fp; - uint8_t *Data; - size_t Size; + uint8_t *data; + long file_size; + size_t size; + FILE *file; if (argc != 2) { return 1; } - //opens the file, get its size, and reads it into a buffer - fp = fopen(argv[1], "rb"); - if (fp == NULL) { + file = fopen(argv[1], "rb"); + if (file == NULL) { return 2; } - if (fseek(fp, 0L, SEEK_END) != 0) { - fclose(fp); + if (fseek(file, 0, SEEK_END) != 0) { + fclose(file); return 2; } - Size = ftell(fp); - if (Size == (size_t) -1) { - fclose(fp); + file_size = ftell(file); + if (file_size < 0) { + fclose(file); return 2; } - if (fseek(fp, 0L, SEEK_SET) != 0) { - fclose(fp); + if (file_size > MAX_INPUT_SIZE) { + fclose(file); + return 3; + } + if (fseek(file, 0, SEEK_SET) != 0) { + fclose(file); return 2; } - Data = malloc(Size); - if (Data == NULL) { - fclose(fp); + size = (size_t)file_size; + data = malloc(size == 0 ? 1 : size); + if (data == NULL) { + fclose(file); return 2; } - if (fread(Data, Size, 1, fp) != 1) { - fclose(fp); + if (size != 0 && fread(data, 1, size, file) != size) { + free(data); + fclose(file); return 2; } - //lauch fuzzer - LLVMFuzzerTestOneInput(Data, Size); - fclose(fp); + LLVMFuzzerTestOneInput(data, size); + free(data); + fclose(file); return 0; } - diff --git a/tests/internal/test_alloc_failure.c b/tests/internal/test_alloc_failure.c new file mode 100644 index 0000000000..c7d44d1bb9 --- /dev/null +++ b/tests/internal/test_alloc_failure.c @@ -0,0 +1,368 @@ +#include "unicorn_test.h" +#include "uc_priv.h" + +#include + +typedef enum TestMapKind { + TEST_MAP_RAM, + TEST_MAP_HOST, + TEST_MAP_MMIO, +} TestMapKind; + +static void fail_next_allocation(uc_engine *uc, UcTestAllocFailSite site) +{ + TEST_CHECK(uc->test_alloc_fail_site == UC_TEST_ALLOC_FAIL_NONE); + uc->test_alloc_fail_site = site; +} + +static void check_failure_consumed(uc_engine *uc, + UcTestAllocFailSite site) +{ + if (!TEST_CHECK(uc->test_alloc_fail_site == UC_TEST_ALLOC_FAIL_NONE)) { + TEST_MSG("failure site %d was not reached", (int)site); + } +} + +static bool fail_flatview_copy(struct uc_struct *uc, FlatView *dst, + FlatView *src, bool update_dispatcher) +{ + (void)uc; + (void)dst; + (void)src; + (void)update_dispatcher; + return false; +} + +static bool fail_flatview_reserve(FlatView *view, unsigned int count) +{ + (void)view; + (void)count; + return false; +} + +static MemoryRegion *fail_guest_memory_cow(struct uc_struct *uc, + UcMapping *mapping, + MemoryRegion *current, + hwaddr begin, size_t size) +{ + (void)uc; + (void)mapping; + (void)current; + (void)begin; + (void)size; + return NULL; +} + +static void check_regions(uc_engine *uc, const uc_mem_region *expected, + uint32_t expected_count) +{ + uc_mem_region *actual = NULL; + uint32_t actual_count = 0; + uc_err err = uc_mem_regions(uc, &actual, &actual_count); + uint32_t i; + + if (!TEST_CHECK(err == UC_ERR_OK)) { + TEST_MSG("uc_mem_regions failed: %s", uc_strerror(err)); + return; + } + if (TEST_CHECK(actual_count == expected_count)) { + for (i = 0; i < expected_count; i++) { + TEST_CHECK(actual[i].begin == expected[i].begin); + TEST_CHECK(actual[i].end == expected[i].end); + TEST_CHECK(actual[i].perms == expected[i].perms); + } + } + OK(uc_free(actual)); +} + +static void check_byte(uc_engine *uc, uint64_t address, uint8_t expected) +{ + uint8_t actual = 0; + + OK(uc_mem_read(uc, address, &actual, sizeof(actual))); + TEST_CHECK(actual == expected); +} + +static void check_unmapped(uc_engine *uc, uint64_t address) +{ + uint8_t value = 0; + + uc_assert_err(UC_ERR_READ_UNMAPPED, + uc_mem_read(uc, address, &value, sizeof(value))); +} + +static uc_err map_memory(uc_engine *uc, TestMapKind kind, uint64_t address, + void *host_memory) +{ + switch (kind) { + case TEST_MAP_RAM: + return uc_mem_map(uc, address, 0x1000, UC_PROT_ALL); + case TEST_MAP_HOST: + return uc_mem_map_ptr(uc, address, 0x1000, UC_PROT_ALL, host_memory); + case TEST_MAP_MMIO: + return uc_mmio_map(uc, address, 0x1000, NULL, NULL, NULL, NULL); + default: + return UC_ERR_ARG; + } +} + +static void run_map_failure(UcTestAllocFailSite site) +{ + const uint64_t address = 0x1000; + TestMapKind kind; + + for (kind = TEST_MAP_RAM; kind <= TEST_MAP_MMIO; kind++) { + const uint32_t perms = kind == TEST_MAP_MMIO ? 0 : UC_PROT_ALL; + const uc_mem_region expected = {address, address + 0xfff, perms}; + uint8_t host_memory[0x1000]; + uc_engine *uc; + uc_err err; + + memset(host_memory, 0, sizeof(host_memory)); + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + fail_next_allocation(uc, site); + err = map_memory(uc, kind, address, host_memory); + if (!TEST_CHECK(err == UC_ERR_NOMEM)) { + TEST_MSG("map kind %d returned %s", (int)kind, uc_strerror(err)); + } + check_failure_consumed(uc, site); + check_regions(uc, NULL, 0); + check_unmapped(uc, address); + + OK(map_memory(uc, kind, address, host_memory)); + check_regions(uc, &expected, 1); + OK(uc_mem_unmap(uc, address, 0x1000)); + OK(uc_close(uc)); + } +} + +static void test_map_block_reservation_failure(void) +{ + run_map_failure(UC_TEST_ALLOC_FAIL_MAPPED_BLOCKS); +} + +static void test_mapping_record_allocation_failure(void) +{ + run_map_failure(UC_TEST_ALLOC_FAIL_MAPPING_RECORD); +} + +static void run_context_save_failure(UcTestAllocFailSite site, + bool fail_ranges) +{ + const uint64_t saved_address = 0x1000; + const uint64_t current_address = 0x3000; + const uint8_t saved_value = 0x11; + const uint8_t current_value = 0x22; + const uint8_t extra_value = 0x33; + const uc_mem_region current_regions[] = { + {saved_address, saved_address + 0xfff, UC_PROT_ALL}, + {current_address, current_address + 0xfff, UC_PROT_ALL}, + }; + const uc_mem_region saved_region = { + saved_address, saved_address + 0xfff, UC_PROT_ALL}; + uc_flatview_copy_t flatview_copy = NULL; + uc_context *context; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_mem_map(uc, saved_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, saved_address, &saved_value, sizeof(saved_value))); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + OK(uc_mem_write(uc, saved_address, ¤t_value, + sizeof(current_value))); + OK(uc_mem_map(uc, current_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, current_address, &extra_value, sizeof(extra_value))); + + if (fail_ranges) { + flatview_copy = uc->flatview_copy; + uc->flatview_copy = fail_flatview_copy; + } else { + fail_next_allocation(uc, site); + } + uc_assert_err(UC_ERR_NOMEM, uc_context_save(uc, context)); + if (fail_ranges) { + uc->flatview_copy = flatview_copy; + } else { + check_failure_consumed(uc, site); + } + check_regions(uc, current_regions, 2); + check_byte(uc, saved_address, current_value); + check_byte(uc, current_address, extra_value); + + OK(uc_context_restore(uc, context)); + check_regions(uc, &saved_region, 1); + check_byte(uc, saved_address, saved_value); + check_unmapped(uc, current_address); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + +static void test_context_save_reservation_failures(void) +{ + static const UcTestAllocFailSite sites[] = { + UC_TEST_ALLOC_FAIL_CONTEXT_VIEW, + UC_TEST_ALLOC_FAIL_CONTEXT_MAPPINGS, + UC_TEST_ALLOC_FAIL_CONTEXT_REGIONS, + }; + size_t i; + + for (i = 0; i < sizeof(sites) / sizeof(sites[0]); i++) { + run_context_save_failure(sites[i], false); + } + run_context_save_failure(UC_TEST_ALLOC_FAIL_NONE, true); +} + +static void run_context_restore_failure(UcTestAllocFailSite site, + bool fail_ranges) +{ + const uint64_t first_saved_address = 0x1000; + const uint64_t second_saved_address = 0x3000; + const uint64_t current_address = 0x5000; + const uint8_t first_saved_value = 0x41; + const uint8_t second_saved_value = 0x52; + const uint8_t current_value = 0x63; + const uc_mem_region saved_regions[] = { + {first_saved_address, first_saved_address + 0xfff, UC_PROT_ALL}, + {second_saved_address, second_saved_address + 0xfff, UC_PROT_ALL}, + }; + const uc_mem_region current_region = { + current_address, current_address + 0xfff, UC_PROT_ALL}; + uc_flatview_reserve_t flatview_reserve = NULL; + uc_context *context; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_mem_map(uc, first_saved_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_map(uc, second_saved_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, first_saved_address, &first_saved_value, + sizeof(first_saved_value))); + OK(uc_mem_write(uc, second_saved_address, &second_saved_value, + sizeof(second_saved_value))); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + OK(uc_mem_unmap(uc, first_saved_address, 0x1000)); + OK(uc_mem_unmap(uc, second_saved_address, 0x1000)); + OK(uc_mem_map(uc, current_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, current_address, ¤t_value, + sizeof(current_value))); + check_regions(uc, ¤t_region, 1); + + if (site == UC_TEST_ALLOC_FAIL_MAPPED_BLOCKS) { + TEST_CHECK(uc->mapped_block_count == 1); + /* Force restore to grow an otherwise valid active-mapping array. */ + uc->mapped_block_capacity = uc->mapped_block_count; + } + if (fail_ranges) { + flatview_reserve = uc->flatview_reserve; + uc->flatview_reserve = fail_flatview_reserve; + } else { + fail_next_allocation(uc, site); + } + uc_assert_err(UC_ERR_NOMEM, uc_context_restore(uc, context)); + if (fail_ranges) { + uc->flatview_reserve = flatview_reserve; + } else { + check_failure_consumed(uc, site); + } + check_regions(uc, ¤t_region, 1); + check_byte(uc, current_address, current_value); + check_unmapped(uc, first_saved_address); + check_unmapped(uc, second_saved_address); + + OK(uc_context_restore(uc, context)); + check_regions(uc, saved_regions, 2); + check_byte(uc, first_saved_address, first_saved_value); + check_byte(uc, second_saved_address, second_saved_value); + check_unmapped(uc, current_address); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + +static void test_context_restore_view_failure(void) +{ + run_context_restore_failure(UC_TEST_ALLOC_FAIL_RESTORE_VIEW, false); +} + +static void test_context_restore_mapping_reservation_failure(void) +{ + run_context_restore_failure(UC_TEST_ALLOC_FAIL_MAPPED_BLOCKS, false); +} + +static void test_context_restore_range_reservation_failure(void) +{ + run_context_restore_failure(UC_TEST_ALLOC_FAIL_NONE, true); +} + +static void test_guest_cow_failure_exit(void) +{ + const uint64_t code_address = 0x1000; + const uint64_t data_address = 0x3000; + const uint8_t code[] = { + 0xc7, 0x05, 0x00, 0x30, 0x00, 0x00, + 0x78, 0x56, 0x34, 0x12, /* mov dword [0x3000],0x12345678 */ + 0x40, /* inc eax */ + }; + const uint32_t initial_value = 0x44332211; + const uint32_t stored_value = 0x12345678; + uc_mem_cow_t memory_cow; + uc_context *context; + uint32_t actual = 0; + uint32_t eax = 0; + uint32_t eip = 0; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, code_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_address, code, sizeof(code))); + OK(uc_mem_write(uc, data_address, &initial_value, + sizeof(initial_value))); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + memory_cow = uc->memory_cow; + uc->memory_cow = fail_guest_memory_cow; + uc_assert_err(UC_ERR_NOMEM, + uc_emu_start(uc, code_address, + code_address + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_mem_read(uc, data_address, &actual, sizeof(actual))); + TEST_CHECK(eip == code_address); + TEST_CHECK(eax == 0); + TEST_CHECK(actual == initial_value); + + uc->memory_cow = memory_cow; + OK(uc_emu_start(uc, eip, code_address + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_mem_read(uc, data_address, &actual, sizeof(actual))); + TEST_CHECK(eax == 1); + TEST_CHECK(actual == stored_value); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + +TEST_LIST = { + {"test_map_block_reservation_failure", + test_map_block_reservation_failure}, + {"test_mapping_record_allocation_failure", + test_mapping_record_allocation_failure}, + {"test_context_save_reservation_failures", + test_context_save_reservation_failures}, + {"test_context_restore_view_failure", + test_context_restore_view_failure}, + {"test_context_restore_mapping_reservation_failure", + test_context_restore_mapping_reservation_failure}, + {"test_context_restore_range_reservation_failure", + test_context_restore_range_reservation_failure}, + {"test_guest_cow_failure_exit", test_guest_cow_failure_exit}, + {NULL, NULL}}; diff --git a/tests/internal/test_arm_m_profile.c b/tests/internal/test_arm_m_profile.c new file mode 100644 index 0000000000..e04a8439fc --- /dev/null +++ b/tests/internal/test_arm_m_profile.c @@ -0,0 +1,154 @@ +#include "unicorn_test.h" +#include "uc_priv.h" +#include "target/arm/cpu.h" + +#include + +static const uint64_t m_profile_code_start = 0x1000; +static const uint64_t m_profile_code_len = 0x4000; + +static void setup_m_profile(uc_engine **uc, uc_cpu_arm cpu, + const uint8_t *code, size_t code_size) +{ + OK(uc_open(UC_ARCH_ARM, UC_MODE_THUMB | UC_MODE_MCLASS, uc)); + OK(uc_ctl_set_cpu_model(*uc, cpu)); + OK(uc_mem_map(*uc, m_profile_code_start, m_profile_code_len, + UC_PROT_ALL)); + OK(uc_mem_write(*uc, m_profile_code_start, code, code_size)); +} + +static uint32_t run_tt_query(uc_engine *uc, uint32_t address, uint8_t op) +{ + uint8_t code[] = { + 0x41, 0xe8, 0x00, 0xf0, /* tt-family r0,r1 */ + }; + uint32_t result = 0; + + code[2] = op << 6; + OK(uc_mem_write(uc, m_profile_code_start, code, sizeof(code))); + OK(uc_reg_write(uc, UC_ARM_REG_R0, &result)); + OK(uc_reg_write(uc, UC_ARM_REG_R1, &address)); + OK(uc_emu_start(uc, m_profile_code_start | 1, + m_profile_code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_ARM_REG_R0, &result)); + + return result; +} + +static void set_sau_region(CPUARMState *env, uint32_t region, + uint32_t base, uint32_t limit, + bool non_secure_callable) +{ + env->sau.rbar[region] = base & ~0x1fU; + env->sau.rlar[region] = (limit & ~0x1fU) | 1U; + if (non_secure_callable) { + env->sau.rlar[region] |= 2U; + } +} + +static void test_arm_m33_sau_tt(void) +{ + const uint32_t tt_secure = 1U << 22; + const uint32_t tt_sregion_valid = 1U << 17; + const uint8_t tt = 0; + const uint8_t tta = 2; + const uint8_t nop[] = { 0x00, 0xbf }; + uc_engine *uc; + ARMCPU *cpu; + CPUARMState *env; + uint32_t result; + + setup_m_profile(&uc, UC_CPU_ARM_CORTEX_M33, nop, sizeof(nop)); + cpu = ARM_CPU(uc->cpu); + env = &cpu->env; + if (!TEST_CHECK(cpu->sau_sregion >= 2)) { + OK(uc_close(uc)); + return; + } + + env->sau.ctrl = 0; + memset(env->sau.rbar, 0, + sizeof(*env->sau.rbar) * cpu->sau_sregion); + memset(env->sau.rlar, 0, + sizeof(*env->sau.rlar) * cpu->sau_sregion); + result = run_tt_query(uc, 0x2000, tt); + TEST_CHECK((result & tt_secure) != 0); + TEST_CHECK((result & tt_sregion_valid) == 0); + + env->sau.ctrl = 2; + result = run_tt_query(uc, 0x2000, tt); + TEST_CHECK((result & tt_secure) == 0); + TEST_CHECK((result & tt_sregion_valid) == 0); + + env->sau.ctrl = 1; + set_sau_region(env, 0, 0x2000, 0x2fff, false); + result = run_tt_query(uc, 0x2000, tt); + TEST_CHECK((result & tt_secure) == 0); + TEST_CHECK((result & tt_sregion_valid) != 0); + TEST_CHECK(((result >> 8) & 0xff) == 0); + + set_sau_region(env, 1, 0x3000, 0x3fff, true); + result = run_tt_query(uc, 0x3000, tt); + TEST_CHECK((result & tt_secure) != 0); + TEST_CHECK((result & tt_sregion_valid) != 0); + TEST_CHECK(((result >> 8) & 0xff) == 1); + + set_sau_region(env, 1, 0x2000, 0x2fff, true); + result = run_tt_query(uc, 0x2000, tt); + TEST_CHECK((result & tt_secure) != 0); + TEST_CHECK((result & tt_sregion_valid) == 0); + + result = run_tt_query(uc, 0xe000e010, tt); + TEST_CHECK((result & tt_secure) != 0); + result = run_tt_query(uc, 0xe000e010, tta); + TEST_CHECK((result & tt_secure) == 0); + + OK(uc_close(uc)); +} + +static void test_arm_m55_privileged_pxn(void) +{ + const uint8_t code[] = { + 0x2a, 0x20, /* movs r0,#42 */ + }; + uc_engine *uc; + ARMCPU *cpu; + CPUARMState *env; + uint32_t control = 0; + uint32_t r0 = 0; + uc_err err; + + setup_m_profile(&uc, UC_CPU_ARM_CORTEX_M55, code, sizeof(code)); + OK(uc_reg_write(uc, UC_ARM_REG_CONTROL, &control)); + cpu = ARM_CPU(uc->cpu); + env = &cpu->env; + if (!TEST_CHECK(cpu->pmsav7_dregion >= 1)) { + OK(uc_close(uc)); + return; + } + + memset(env->pmsav8.rbar[M_REG_S], 0, + sizeof(*env->pmsav8.rbar[M_REG_S]) * cpu->pmsav7_dregion); + memset(env->pmsav8.rlar[M_REG_S], 0, + sizeof(*env->pmsav8.rlar[M_REG_S]) * cpu->pmsav7_dregion); + env->v7m.mpu_ctrl[M_REG_S] = R_V7M_MPU_CTRL_ENABLE_MASK; + env->pmsav8.rbar[M_REG_S][0] = + (uint32_t)m_profile_code_start | (1U << 1); + env->pmsav8.rlar[M_REG_S][0] = + ((uint32_t)(m_profile_code_start + m_profile_code_len - 1) & + ~0x1fU) | + (1U << 4) | 1U; + + err = uc_emu_start(uc, m_profile_code_start | 1, + m_profile_code_start + sizeof(code), 0, 0); + TEST_CHECK_(err == UC_ERR_EXCEPTION, "err=%u", (unsigned)err); + OK(uc_reg_read(uc, UC_ARM_REG_R0, &r0)); + TEST_CHECK_(r0 == 0, "r0=0x%08x", r0); + + OK(uc_close(uc)); +} + +TEST_LIST = { + {"test_arm_m33_sau_tt", test_arm_m33_sau_tt}, + {"test_arm_m55_privileged_pxn", test_arm_m55_privileged_pxn}, + {NULL, NULL}}; diff --git a/tests/regress/001-bad_condition_code_0xe.c b/tests/regress/001-bad_condition_code_0xe.c index bcc51e8bbf..92a9f85947 100644 --- a/tests/regress/001-bad_condition_code_0xe.c +++ b/tests/regress/001-bad_condition_code_0xe.c @@ -1,31 +1,52 @@ #include -#define HARDWARE_ARCHITECTURE UC_ARCH_ARM -#define HARDWARE_MODE 16 -#define MEMORY_STARTING_ADDRESS 8192 -#define MEMORY_SIZE 4096 -#define MEMORY_PERMISSIONS 6 -#define BINARY_CODE "\x56\xe8\x46\x46\x80\xf6\x8c\x56\xff\xbf\xcd\x90\xda\xa0\xed\xe8\x46\x43\x45\xe5\x80\x90\x44\x46\x04" +#define ADDRESS 0x2000 -static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { - printf("hook_code(…) called\n"); +static unsigned int hook_count; + +static void count_instruction(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + (void)uc; + (void)address; + (void)size; + (void)user_data; + hook_count++; } -int main(int argc, char **argv, char **envp) { - uc_engine *uc; - if (uc_open(HARDWARE_ARCHITECTURE, HARDWARE_MODE, &uc)) { - printf("uc_open(…) failed\n"); - return 1; - } - uc_mem_map(uc, MEMORY_STARTING_ADDRESS, MEMORY_SIZE, MEMORY_PERMISSIONS); - if (uc_mem_write(uc, MEMORY_STARTING_ADDRESS, BINARY_CODE, sizeof(BINARY_CODE) - 1)) { - printf("uc_mem_write(…) failed\n"); - return 1; - } - uc_hook trace; - uc_hook_add(uc, &trace, UC_HOOK_CODE, hook_code, NULL, (uint64_t)MEMORY_STARTING_ADDRESS, (uint64_t)(MEMORY_STARTING_ADDRESS + 1)); - printf("uc_emu_start(…)\n"); - uc_emu_start(uc, MEMORY_STARTING_ADDRESS, MEMORY_STARTING_ADDRESS + sizeof(BINARY_CODE) - 1, 0, 0); - printf("done\n"); - return 0; +int main(void) +{ + const uint8_t code[] = { + 0x56, 0xe8, 0x46, 0x46, 0x80, 0xf6, 0x8c, 0x56, 0xff, + 0xbf, 0xcd, 0x90, 0xda, 0xa0, 0xed, 0xe8, 0x46, 0x43, + 0x45, 0xe5, 0x80, 0x90, 0x44, 0x46, 0x04, + }; + uint32_t pc = 0; + uc_engine *uc; + uc_hook hook; + uc_err err; + + err = uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &uc); + if (err != UC_ERR_OK) { + return 1; + } + if (uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_WRITE | UC_PROT_EXEC) != + UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_CODE, count_instruction, NULL, ADDRESS, + ADDRESS + 1) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_WRITE_UNMAPPED || + uc_reg_read(uc, UC_ARM_REG_PC, &pc) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + if (uc_close(uc) != UC_ERR_OK) { + return 1; + } + return pc == ADDRESS + 4 && hook_count == 1 ? 0 : 1; } diff --git a/tests/regress/002-qemu__fatal__unimplemented_control_register_write_0xffb___0x0.c b/tests/regress/002-qemu__fatal__unimplemented_control_register_write_0xffb___0x0.c index 6f8575be82..d805248158 100644 --- a/tests/regress/002-qemu__fatal__unimplemented_control_register_write_0xffb___0x0.c +++ b/tests/regress/002-qemu__fatal__unimplemented_control_register_write_0xffb___0x0.c @@ -1,31 +1,49 @@ #include -#define HARDWARE_ARCHITECTURE UC_ARCH_M68K -#define HARDWARE_MODE 1073741824 -#define MEMORY_STARTING_ADDRESS 8388608 -#define MEMORY_SIZE 2097152 -#define MEMORY_PERMISSIONS 7 -#define BINARY_CODE "\xaf\x80\x4e\x7b\xff\xfb\x80\x4e\x3e\x80" +#define ADDRESS 0x800000 -static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { - printf("hook_code(…) called\n"); +static unsigned int hook_count; + +static void count_instruction(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + (void)uc; + (void)address; + (void)size; + (void)user_data; + hook_count++; } -int main(int argc, char **argv, char **envp) { - uc_engine *uc; - if (uc_open(HARDWARE_ARCHITECTURE, HARDWARE_MODE, &uc)) { - printf("uc_open(…) failed\n"); - return 1; - } - uc_mem_map(uc, MEMORY_STARTING_ADDRESS, MEMORY_SIZE, MEMORY_PERMISSIONS); - if (uc_mem_write(uc, MEMORY_STARTING_ADDRESS, BINARY_CODE, sizeof(BINARY_CODE) - 1)) { - printf("uc_mem_write(…) failed\n"); - return 1; - } - uc_hook trace; - uc_hook_add(uc, &trace, UC_HOOK_CODE, hook_code, NULL, (uint64_t)MEMORY_STARTING_ADDRESS, (uint64_t)(MEMORY_STARTING_ADDRESS + 1)); - printf("uc_emu_start(…)\n"); - uc_emu_start(uc, MEMORY_STARTING_ADDRESS, MEMORY_STARTING_ADDRESS + sizeof(BINARY_CODE) - 1, 0, 0); - printf("done\n"); - return 0; +int main(void) +{ + const uint8_t code[] = { + 0xaf, 0x80, 0x4e, 0x7b, 0xff, 0xfb, 0x80, 0x4e, 0x3e, 0x80, + }; + uint32_t pc = 0; + uc_engine *uc; + uc_hook hook; + uc_err err; + + err = uc_open(UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, &uc); + if (err != UC_ERR_OK) { + return 1; + } + if (uc_mem_map(uc, ADDRESS, 0x200000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_CODE, count_instruction, NULL, ADDRESS, + ADDRESS + 1) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_EXCEPTION || + uc_reg_read(uc, UC_M68K_REG_PC, &pc) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + if (uc_close(uc) != UC_ERR_OK) { + return 1; + } + return pc == ADDRESS + 2 && hook_count != 0 ? 0 : 1; } diff --git a/tests/regress/003-qemu__fatal__wdebug_not_implemented.c b/tests/regress/003-qemu__fatal__wdebug_not_implemented.c index 4e07235be3..0a9bfc5894 100644 --- a/tests/regress/003-qemu__fatal__wdebug_not_implemented.c +++ b/tests/regress/003-qemu__fatal__wdebug_not_implemented.c @@ -1,31 +1,83 @@ +#include + #include -#define HARDWARE_ARCHITECTURE UC_ARCH_M68K -#define HARDWARE_MODE 1073741824 -#define MEMORY_STARTING_ADDRESS 1048576 -#define MEMORY_SIZE 403456 -#define MEMORY_PERMISSIONS 7 -#define BINARY_CODE "\x42\xc7\xfb\xfb\x54\x36" +#define CODE_ADDRESS UINT64_C(0x100000) +#define PAGE_SIZE UINT64_C(0x1000) + +static unsigned int hook_count; -static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { - printf("hook_code(…) called\n"); +static void count_instruction(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + (void)uc; + (void)address; + (void)size; + (void)user_data; + hook_count++; } -int main(int argc, char **argv, char **envp) { - uc_engine *uc; - if (uc_open(HARDWARE_ARCHITECTURE, HARDWARE_MODE, &uc)) { - printf("uc_open(…) failed\n"); - return 1; - } - uc_mem_map(uc, MEMORY_STARTING_ADDRESS, MEMORY_SIZE, MEMORY_PERMISSIONS); - if (uc_mem_write(uc, MEMORY_STARTING_ADDRESS, BINARY_CODE, sizeof(BINARY_CODE) - 1)) { - printf("uc_mem_write(…) failed\n"); +int main(void) +{ + const uint8_t code[] = {0xfb, 0xfb}; /* wdebug */ + uint32_t sr = 0x2000; + uc_engine *uc = NULL; + uc_hook hook; + uc_err err; + + err = uc_open(UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, &uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_open failed: %s\n", uc_strerror(err)); + return 1; + } + + err = uc_ctl_set_cpu_model(uc, UC_CPU_M68K_CFV4E); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_ctl_set_cpu_model failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_map(uc, CODE_ADDRESS, PAGE_SIZE, UC_PROT_READ | UC_PROT_EXEC); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_map failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_write(uc, CODE_ADDRESS, code, sizeof(code)); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_write failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_reg_write(uc, UC_M68K_REG_SR, &sr); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_write(SR) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_hook_add(uc, &hook, UC_HOOK_CODE, count_instruction, NULL, + CODE_ADDRESS, CODE_ADDRESS + 1); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_hook_add failed: %s\n", uc_strerror(err)); + goto fail; + } + + /* Unsupported guest instructions must not terminate the host process. */ + err = uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + sizeof(code), 0, 1); + if (err != UC_ERR_EXCEPTION) { + fprintf(stderr, "expected UC_ERR_EXCEPTION, got %s\n", + uc_strerror(err)); + goto fail; + } + if (hook_count == 0) { + fprintf(stderr, "bounded code hook was not invoked\n"); + goto fail; + } + + err = uc_close(uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_close failed: %s\n", uc_strerror(err)); + return 1; + } + return 0; + +fail: + uc_close(uc); return 1; - } - uc_hook trace; - uc_hook_add(uc, &trace, UC_HOOK_CODE, hook_code, NULL, (uint64_t)MEMORY_STARTING_ADDRESS, (uint64_t)(MEMORY_STARTING_ADDRESS + 1)); - printf("uc_emu_start(…)\n"); - uc_emu_start(uc, MEMORY_STARTING_ADDRESS, MEMORY_STARTING_ADDRESS + sizeof(BINARY_CODE) - 1, 0, 0); - printf("done\n"); - return 0; } diff --git a/tests/regress/004-segmentation_fault_1.c b/tests/regress/004-segmentation_fault_1.c index 636cae7efa..42df74bdd1 100644 --- a/tests/regress/004-segmentation_fault_1.c +++ b/tests/regress/004-segmentation_fault_1.c @@ -1,31 +1,50 @@ #include -#define HARDWARE_ARCHITECTURE UC_ARCH_ARM -#define HARDWARE_MODE 16 -#define MEMORY_STARTING_ADDRESS 1024 -#define MEMORY_SIZE 1796096 -#define MEMORY_PERMISSIONS 7 -#define BINARY_CODE "\x20\xbf\xbf\xbf\xbf\xdd\x5d\x74\x5e\x66\x72\x10" +#define ADDRESS 0x400 +#define MAP_SIZE 0x1b6800 -static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { - printf("hook_code(…) called\n"); +static unsigned int hook_count; + +static void count_instruction(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + (void)uc; + (void)address; + (void)size; + (void)user_data; + hook_count++; } -int main(int argc, char **argv, char **envp) { - uc_engine *uc; - if (uc_open(HARDWARE_ARCHITECTURE, HARDWARE_MODE, &uc)) { - printf("uc_open(…) failed\n"); - return 1; - } - uc_mem_map(uc, MEMORY_STARTING_ADDRESS, MEMORY_SIZE, MEMORY_PERMISSIONS); - if (uc_mem_write(uc, MEMORY_STARTING_ADDRESS, BINARY_CODE, sizeof(BINARY_CODE) - 1)) { - printf("uc_mem_write(…) failed\n"); - return 1; - } - uc_hook trace; - uc_hook_add(uc, &trace, UC_HOOK_CODE, hook_code, NULL, (uint64_t)MEMORY_STARTING_ADDRESS, (uint64_t)(MEMORY_STARTING_ADDRESS + 1)); - printf("uc_emu_start(…)\n"); - uc_emu_start(uc, MEMORY_STARTING_ADDRESS, MEMORY_STARTING_ADDRESS + sizeof(BINARY_CODE) - 1, 0, 0); - printf("done\n"); - return 0; +int main(void) +{ + const uint8_t code[] = { + 0x20, 0xbf, 0xbf, 0xbf, 0xbf, 0xdd, 0x5d, 0x74, 0x5e, 0x66, 0x72, 0x10, + }; + uint32_t pc = 0; + uc_engine *uc; + uc_hook hook; + uc_err err; + + err = uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &uc); + if (err != UC_ERR_OK) { + return 1; + } + if (uc_mem_map(uc, ADDRESS, MAP_SIZE, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_CODE, count_instruction, NULL, ADDRESS, + ADDRESS + 1) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_READ_UNMAPPED || + uc_reg_read(uc, UC_ARM_REG_PC, &pc) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + if (uc_close(uc) != UC_ERR_OK) { + return 1; + } + return pc == ADDRESS + 4 && hook_count == 1 ? 0 : 1; } diff --git a/tests/regress/005-qemu__fatal__illegal_instruction__0000___00000404.c b/tests/regress/005-qemu__fatal__illegal_instruction__0000___00000404.c index b2bea73c36..06832860d6 100644 --- a/tests/regress/005-qemu__fatal__illegal_instruction__0000___00000404.c +++ b/tests/regress/005-qemu__fatal__illegal_instruction__0000___00000404.c @@ -1,31 +1,87 @@ +#include +#include + #include -#define HARDWARE_ARCHITECTURE UC_ARCH_M68K -#define HARDWARE_MODE 1073741824 -#define MEMORY_STARTING_ADDRESS 1024 -#define MEMORY_SIZE 1044480 -#define MEMORY_PERMISSIONS 5 -#define BINARY_CODE "\x4c\x4c" +#define CODE_ADDRESS UINT64_C(0x400) +#define MAP_SIZE UINT64_C(0x1000) + +static unsigned int hook_count; -static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { - printf("hook_code(…) called\n"); +static void count_instruction(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + (void)uc; + (void)address; + (void)size; + (void)user_data; + hook_count++; } -int main(int argc, char **argv, char **envp) { - uc_engine *uc; - if (uc_open(HARDWARE_ARCHITECTURE, HARDWARE_MODE, &uc)) { - printf("uc_open(…) failed\n"); - return 1; - } - uc_mem_map(uc, MEMORY_STARTING_ADDRESS, MEMORY_SIZE, MEMORY_PERMISSIONS); - if (uc_mem_write(uc, MEMORY_STARTING_ADDRESS, BINARY_CODE, sizeof(BINARY_CODE) - 1)) { - printf("uc_mem_write(…) failed\n"); +int main(void) +{ + const uint8_t code[] = {0x4c, 0x4c}; + uint32_t pc = 0; + uc_engine *uc = NULL; + uc_hook hook; + uc_err err; + + err = uc_open(UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, &uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_open failed: %s\n", uc_strerror(err)); + return 1; + } + err = uc_ctl_set_cpu_model(uc, UC_CPU_M68K_CFV4E); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_ctl_set_cpu_model failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_map(uc, 0, MAP_SIZE, UC_PROT_READ | UC_PROT_EXEC); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_map failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_write(uc, CODE_ADDRESS, code, sizeof(code)); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_write failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_hook_add(uc, &hook, UC_HOOK_CODE, count_instruction, NULL, + CODE_ADDRESS, CODE_ADDRESS + 1); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_hook_add failed: %s\n", uc_strerror(err)); + goto fail; + } + + err = uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + sizeof(code), 0, 1); + if (err != UC_ERR_EXCEPTION) { + fprintf(stderr, "expected UC_ERR_EXCEPTION, got %s\n", + uc_strerror(err)); + goto fail; + } + err = uc_reg_read(uc, UC_M68K_REG_PC, &pc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_read(PC) failed: %s\n", uc_strerror(err)); + goto fail; + } + if (pc != CODE_ADDRESS) { + fprintf(stderr, "expected PC 0x%" PRIx64 ", got 0x%" PRIx32 "\n", + CODE_ADDRESS, pc); + goto fail; + } + if (hook_count == 0) { + fprintf(stderr, "bounded code hook was not invoked\n"); + goto fail; + } + + err = uc_close(uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_close failed: %s\n", uc_strerror(err)); + return 1; + } + return 0; + +fail: + uc_close(uc); return 1; - } - uc_hook trace; - uc_hook_add(uc, &trace, UC_HOOK_CODE, hook_code, NULL, (uint64_t)MEMORY_STARTING_ADDRESS, (uint64_t)(MEMORY_STARTING_ADDRESS + 1)); - printf("uc_emu_start(…)\n"); - uc_emu_start(uc, MEMORY_STARTING_ADDRESS, MEMORY_STARTING_ADDRESS + sizeof(BINARY_CODE) - 1, 0, 0); - printf("done\n"); - return 0; } diff --git a/tests/regress/006-qemu__fatal__illegal_instruction__0421___00040026.c b/tests/regress/006-qemu__fatal__illegal_instruction__0421___00040026.c index 607c9d5dca..5ddb93fe78 100644 --- a/tests/regress/006-qemu__fatal__illegal_instruction__0421___00040026.c +++ b/tests/regress/006-qemu__fatal__illegal_instruction__0421___00040026.c @@ -1,31 +1,95 @@ +#include +#include + #include -#define HARDWARE_ARCHITECTURE UC_ARCH_M68K -#define HARDWARE_MODE 1073741824 -#define MEMORY_STARTING_ADDRESS 262144 -#define MEMORY_SIZE 403456 -#define MEMORY_PERMISSIONS 7 -#define BINARY_CODE "\xe2\x86\x09\xbc\xf2\x17\x09\xca\xca\xca\xca\x09\x09\x09\xf2\x17\x09\x20\x09\x09\xf2\x08\x09\x03\x09\xca\x6b\x6b\x6b\x1e\xca\xca\x86\x09\x09\xf2\x17\x09\x04\x21\x09\x09\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf2" +#define CODE_ADDRESS UINT64_C(0x40000) +#define MAP_SIZE UINT64_C(0x63000) +#define FAULT_OFFSET UINT32_C(0x22) + +static unsigned int hook_count; -static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { - printf("hook_code(…) called\n"); +static void count_instruction(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + (void)uc; + (void)address; + (void)size; + (void)user_data; + hook_count++; } -int main(int argc, char **argv, char **envp) { - uc_engine *uc; - if (uc_open(HARDWARE_ARCHITECTURE, HARDWARE_MODE, &uc)) { - printf("uc_open(…) failed\n"); - return 1; - } - uc_mem_map(uc, MEMORY_STARTING_ADDRESS, MEMORY_SIZE, MEMORY_PERMISSIONS); - if (uc_mem_write(uc, MEMORY_STARTING_ADDRESS, BINARY_CODE, sizeof(BINARY_CODE) - 1)) { - printf("uc_mem_write(…) failed\n"); +int main(void) +{ + const uint8_t code[] = { + 0xe2, 0x86, 0x09, 0xbc, 0xf2, 0x17, 0x09, 0xca, 0xca, 0xca, 0xca, + 0x09, 0x09, 0x09, 0xf2, 0x17, 0x09, 0x20, 0x09, 0x09, 0xf2, 0x08, + 0x09, 0x03, 0x09, 0xca, 0x6b, 0x6b, 0x6b, 0x1e, 0xca, 0xca, 0x86, + 0x09, 0x09, 0xf2, 0x17, 0x09, 0x04, 0x21, 0x09, 0x09, 0xf4, 0xf4, + 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, + 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf2, + }; + uint32_t pc = 0; + uc_engine *uc = NULL; + uc_hook hook; + uc_err err; + + err = uc_open(UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, &uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_open failed: %s\n", uc_strerror(err)); + return 1; + } + err = uc_ctl_set_cpu_model(uc, UC_CPU_M68K_CFV4E); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_ctl_set_cpu_model failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_map(uc, CODE_ADDRESS, MAP_SIZE, UC_PROT_ALL); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_map failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_write(uc, CODE_ADDRESS, code, sizeof(code)); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_write failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_hook_add(uc, &hook, UC_HOOK_CODE, count_instruction, NULL, + CODE_ADDRESS, CODE_ADDRESS + 1); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_hook_add failed: %s\n", uc_strerror(err)); + goto fail; + } + + err = uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + sizeof(code), 0, 64); + if (err != UC_ERR_EXCEPTION) { + fprintf(stderr, "expected UC_ERR_EXCEPTION, got %s\n", + uc_strerror(err)); + goto fail; + } + err = uc_reg_read(uc, UC_M68K_REG_PC, &pc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_read(PC) failed: %s\n", uc_strerror(err)); + goto fail; + } + if (pc != CODE_ADDRESS + FAULT_OFFSET) { + fprintf(stderr, "expected PC 0x%" PRIx64 ", got 0x%" PRIx32 "\n", + CODE_ADDRESS + FAULT_OFFSET, pc); + goto fail; + } + if (hook_count == 0) { + fprintf(stderr, "bounded code hook was not invoked\n"); + goto fail; + } + + err = uc_close(uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_close failed: %s\n", uc_strerror(err)); + return 1; + } + return 0; + +fail: + uc_close(uc); return 1; - } - uc_hook trace; - uc_hook_add(uc, &trace, UC_HOOK_CODE, hook_code, NULL, (uint64_t)MEMORY_STARTING_ADDRESS, (uint64_t)(MEMORY_STARTING_ADDRESS + 1)); - printf("uc_emu_start(…)\n"); - uc_emu_start(uc, MEMORY_STARTING_ADDRESS, MEMORY_STARTING_ADDRESS + sizeof(BINARY_CODE) - 1, 0, 0); - printf("done\n"); - return 0; } diff --git a/tests/regress/00opcode_uc_crash.c b/tests/regress/00opcode_uc_crash.c index 92cf350d49..73adcee023 100644 --- a/tests/regress/00opcode_uc_crash.c +++ b/tests/regress/00opcode_uc_crash.c @@ -1,69 +1,72 @@ -#include +#include #include -#include #include -#define X86_CODE32 "\x00" // add byte ptr ds:[eax],al -#define ADDRESS 0x1000000 +#define CODE_ADDRESS UINT64_C(0x1000000) +#define DATA_ADDRESS UINT32_C(0x1000008) +#define MAP_SIZE UINT64_C(0x1000) -static void VM_exec(void) +int main(void) { - uc_engine *uc; + const uint8_t code[] = {0x00, 0x00}; /* add byte ptr [eax], al */ + uint32_t eax = DATA_ADDRESS; + uint32_t eip = 0; + uint32_t value = 0; + uc_engine *uc = NULL; uc_err err; - uint32_t tmp; - unsigned int r_eax; - r_eax = 0x1000008; - - // Initialize emulator in X86-32bit mode err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); - if(err) - { - printf("Failed on uc_open() with error returned: %s\n", uc_strerror(err)); - return; + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_open failed: %s\n", uc_strerror(err)); + return 1; } - - err = uc_mem_map(uc, ADDRESS, (4 * 1024 * 1024), UC_PROT_ALL); - if(err != UC_ERR_OK) - { - printf("Failed to map memory %s\n", uc_strerror(err)); - return; + err = uc_mem_map(uc, CODE_ADDRESS, MAP_SIZE, UC_PROT_ALL); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_map failed: %s\n", uc_strerror(err)); + goto fail; } - - // write machine code to be emulated to memory - err = uc_mem_write(uc, ADDRESS, X86_CODE32, sizeof(X86_CODE32) - 1); - if(err != UC_ERR_OK) - { - printf("Failed to write emulation code to memory, quit!: %s(len %zu)\n", uc_strerror(err), sizeof(X86_CODE32) - 1); - return; + err = uc_mem_write(uc, CODE_ADDRESS, code, sizeof(code)); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_write failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_reg_write(uc, UC_X86_REG_EAX, &eax); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_write(EAX) failed: %s\n", uc_strerror(err)); + goto fail; } - // initialize machine registers - uc_reg_write(uc, UC_X86_REG_EAX, &r_eax); - - // emulate machine code in infinite time - err = uc_emu_start(uc, ADDRESS, ADDRESS + (sizeof(X86_CODE32) - 1), 0, 0); - if(err) - { - printf("Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - - uc_close(uc); - return; + err = uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + sizeof(code), 0, 1); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_emu_start failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_read(uc, DATA_ADDRESS, &value, sizeof(value)); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_read failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_reg_read(uc, UC_X86_REG_EIP, &eip); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_read(EIP) failed: %s\n", uc_strerror(err)); + goto fail; + } + if (value != 8 || eip != CODE_ADDRESS + sizeof(code)) { + fprintf(stderr, + "unexpected result: value=%" PRIu32 ", EIP=0x%" PRIx32 "\n", + value, eip); + goto fail; } - if (!uc_mem_read(uc, ADDRESS+8, &tmp, sizeof(tmp))) - printf(">>> Read 4 bytes from [0x%08X] = 0x%08X\n", ADDRESS+8, tmp); //should contain the byte '8' - else - printf(">>> Failed to read 4 bytes from [0x%08X]\n", ADDRESS+8); + err = uc_close(uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_close failed: %s\n", uc_strerror(err)); + return 1; + } + return 0; +fail: uc_close(uc); - - puts("No crash. Yay!"); -} - -int main(int argc, char *argv[]) -{ - VM_exec(); - return 0; + return 1; } diff --git a/tests/regress/CMakeLists.txt b/tests/regress/CMakeLists.txt new file mode 100644 index 0000000000..b47d572bf9 --- /dev/null +++ b/tests/regress/CMakeLists.txt @@ -0,0 +1,105 @@ +set(UNICORN_REGRESSION_TARGETS) + +function(add_unicorn_regression test_name architecture) + string(MAKE_C_IDENTIFIER "regress_${test_name}" test_target) + add_executable(${test_target} + ${CMAKE_CURRENT_LIST_DIR}/${test_name}.c + ) + target_compile_options(${test_target} PRIVATE + ${UNICORN_COMPILE_OPTIONS} + ) + target_link_libraries(${test_target} PRIVATE + ${SAMPLES_LIB} + ) + add_test(NAME regression.${test_name} COMMAND ${test_target}) + set_tests_properties(regression.${test_name} PROPERTIES + LABELS "regression;${architecture}" + TIMEOUT 30 + ) + if(ANDROID_ABI) + file(APPEND ${CMAKE_BINARY_DIR}/adb.sh + "adb push ${test_target} /data/local/tmp/build/\n") + file(APPEND ${CMAKE_BINARY_DIR}/adb.sh + "adb shell \"chmod +x /data/local/tmp/build/${test_target}\"\n") + file(APPEND ${CMAKE_BINARY_DIR}/adb.sh + "adb shell 'LD_LIBRARY_PATH=/data/local/tmp/build:$LD_LIBRARY_PATH /data/local/tmp/build/${test_target}' || exit -1\n") + endif() + list(APPEND UNICORN_REGRESSION_TARGETS ${test_target}) + set(UNICORN_REGRESSION_TARGETS ${UNICORN_REGRESSION_TARGETS} + PARENT_SCOPE) +endfunction() + +if(UNICORN_HAS_X86) + set(X86_REGRESSION_TESTS + 00opcode_uc_crash + block_test + cross_page_smc + eflags_noset + eflags_nosync + emu_clear_errors + exit_pending_order + hook_extrainvoke + invalid_read_in_cpu_tb_exec + invalid_read_in_tb_flush_x86_64 + invalid_write_in_cpu_tb_exec_x86_64 + map_crash + map_write + memory_hook_stop + nested_stop_state + nr_mem_test + rep_movsb + ro_mem_test + rw_hookstack + sigill + sigill2 + sysenter_hook_x86 + x86_16_segfault + x86_vex + ) + foreach(TEST_NAME ${X86_REGRESSION_TESTS}) + add_unicorn_regression(${TEST_NAME} x86) + endforeach() +endif() + +if(UNICORN_HAS_ARM) + set(ARM_REGRESSION_TESTS + 001-bad_condition_code_0xe + 004-segmentation_fault_1 + arm_enable_vfp + timeout_segfault + ) + foreach(TEST_NAME ${ARM_REGRESSION_TESTS}) + add_unicorn_regression(${TEST_NAME} arm) + endforeach() +endif() + +if(UNICORN_HAS_M68K) + set(M68K_REGRESSION_TESTS + 002-qemu__fatal__unimplemented_control_register_write_0xffb___0x0 + 003-qemu__fatal__wdebug_not_implemented + 005-qemu__fatal__illegal_instruction__0000___00000404 + 006-qemu__fatal__illegal_instruction__0421___00040026 + ) + foreach(TEST_NAME ${M68K_REGRESSION_TESTS}) + add_unicorn_regression(${TEST_NAME} m68k) + endforeach() +endif() + +if(UNICORN_HAS_MIPS) + set(MIPS_REGRESSION_TESTS + emu_stop_in_hook_overrun + mips_branch_likely_issue + mips_delay_slot_code_hook + mips_invalid_read_of_size_4_when_tracing + mips_kseg0_1 + threaded_emu_start + ) + foreach(TEST_NAME ${MIPS_REGRESSION_TESTS}) + add_unicorn_regression(${TEST_NAME} mips) + endforeach() + set_tests_properties(regression.threaded_emu_start PROPERTIES TIMEOUT 45) +endif() + +if(UNICORN_HAS_SPARC) + add_unicorn_regression(sparc_jump_to_zero sparc) +endif() diff --git a/tests/regress/Makefile b/tests/regress/Makefile deleted file mode 100644 index 49f0dfef70..0000000000 --- a/tests/regress/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -CFLAGS += -Wall -Werror -I../../include -CFLAGS += -D__USE_MINGW_ANSI_STDIO=1 -LDLIBS += -L../../ -lm -lunicorn - -UNAME_S := $(shell uname -s) -LDLIBS += -pthread -ifeq ($(UNAME_S), Linux) -LDLIBS += -lrt -endif - -EXECUTE_VARS = LD_LIBRARY_PATH=../../cmocka/src:../../ DYLD_LIBRARY_PATH=../../ - -TESTS_SOURCE = $(wildcard *.c) -TESTS = $(TESTS_SOURCE:%.c=%) - -.PHONY: all clean test - -test: $(TESTS) - -all: $(TESTS) - -clean: - rm -f $(TESTS) diff --git a/tests/regress/arm_enable_vfp.c b/tests/regress/arm_enable_vfp.c index a1c5500d8b..104ed2dba4 100644 --- a/tests/regress/arm_enable_vfp.c +++ b/tests/regress/arm_enable_vfp.c @@ -1,65 +1,44 @@ #include -#include -#include -#include #define ADDRESS 0x1000 -#define ARM_VMOV "\xC0\xEF\x10\x00" // VMOV.I32 D16, #0 ; Vector Move int main(void) { + /* vadd.f32 s0, s1, s2 */ + const uint8_t code[] = {0x81, 0x0a, 0x30, 0xee}; + uint32_t s0 = 0; + uint32_t s1 = 0x3f800000; + uint32_t s2 = 0x40000000; + uint32_t cpacr = 0; + uint32_t fpexc = 1U << 30; uc_engine *uc; uc_err err; err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc); - if (err) { - printf("uc_open %d\n", err); + if (err != UC_ERR_OK) { return 1; } - - uint64_t tmp_val; - - err = uc_reg_read(uc, UC_ARM_REG_C1_C0_2, &tmp_val); - if (err) { - printf("uc_open %d\n", err); - return 1; - } - - tmp_val = tmp_val | (0xf << 20); - err = uc_reg_write(uc, UC_ARM_REG_C1_C0_2, &tmp_val); - if (err) { - printf("uc_open %d\n", err); + if (uc_ctl_set_cpu_model(uc, UC_CPU_ARM_CORTEX_A15) != UC_ERR_OK || + uc_reg_read(uc, UC_ARM_REG_C1_C0_2, &cpacr) != UC_ERR_OK) { + uc_close(uc); return 1; } - - size_t enable_vfp = 0x40000000; - err = uc_reg_write(uc, UC_ARM_REG_FPEXC, &enable_vfp); - if (err) { - printf("uc_open %d\n", err); + cpacr |= 0xfU << 20; + if (uc_reg_write(uc, UC_ARM_REG_C1_C0_2, &cpacr) != UC_ERR_OK || + uc_reg_write(uc, UC_ARM_REG_FPEXC, &fpexc) != UC_ERR_OK || + uc_reg_write(uc, UC_ARM_REG_S0, &s0) != UC_ERR_OK || + uc_reg_write(uc, UC_ARM_REG_S1, &s1) != UC_ERR_OK || + uc_reg_write(uc, UC_ARM_REG_S2, &s2) != UC_ERR_OK || + uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 1) != UC_ERR_OK || + uc_reg_read(uc, UC_ARM_REG_S0, &s0) != UC_ERR_OK) { + uc_close(uc); return 1; } - err = uc_mem_map(uc, ADDRESS, 4 * 1024, UC_PROT_ALL); - if (err) { - printf("uc_mem_map %d\n", err); + if (uc_close(uc) != UC_ERR_OK) { return 1; } - - err = uc_mem_write(uc, ADDRESS, ARM_VMOV, sizeof(ARM_VMOV) - 1); - if (err) { - printf("uc_mem_map %s\n", uc_strerror(err)); - return 1; - } - - err = uc_emu_start(uc, ADDRESS, 0, 0, 1); - if (err) { - printf("uc_emu_start: %s\n", uc_strerror(err)); - return 1; - } - - printf("Success\n"); - - uc_close(uc); - - return 0; + return s0 == 0x40400000 ? 0 : 1; } diff --git a/tests/regress/block_test.c b/tests/regress/block_test.c index 48666603bb..907db49e96 100644 --- a/tests/regress/block_test.c +++ b/tests/regress/block_test.c @@ -1,82 +1,92 @@ -#include -#include -#include - +#include +#include #include -static int count = 1; - -// Callback function for tracing code (UC_HOOK_CODE & UC_HOOK_BLOCK) -// @address: address where the code is being executed -// @size: size of machine instruction being executed -// @user_data: user data passed to tracing APIs. -void cb_hookblock(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { - fprintf(stderr, "# >>> Tracing basic block at 0x%"PRIx64", block size = 0x%x\n", address, size); - if (address != 0x1000000 && address != 0x1000200) { - fprintf(stderr, "not ok %d - address != 0x1000000 && address != 0x1000200\n", count++); - _exit(1); - } - fprintf(stderr, "ok %d - address (0x%x) is start of basic block\n", count++, (uint32_t)address); - if (size != 0x200) { - fprintf(stderr, "not ok %d - basic block size != 0x200\n", count++); - _exit(1); - } - fprintf(stderr, "ok %d - basic block size is correct\n", count++); -} - -int main(void) { - uc_engine *uc; - - fprintf(stderr, "# basic block callback test\n"); - fprintf(stderr, "# there are only two basic blocks 0x1000000-0x10001ff and 0x1000200-0x10003ff\n"); - - uc_err err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_open\n", count++); +#define CODE_ADDRESS UINT64_C(0x1000000) +#define LOOP_ADDRESS (CODE_ADDRESS + UINT64_C(4)) - err = uc_mem_map(uc, 0x1000000, 4096, UC_PROT_ALL); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_mem_map\n", count++); +typedef struct BlockState { + unsigned int count; + bool failed; +} BlockState; - uint8_t code[1024]; - //build a program that consists of 1019 nops followed by a jump -512 - //this program contains exactly 2 basic blocks, a block of 512 nops, followed - //by a loop body containing 507 nops and jump to the top of the loop - //the first basic block begins at address 0x1000000, and the second - //basic block begins at address 0x1000200 - memset(code, 0x90, sizeof(code)); - memcpy(code + 1024 - 5, "\xe9\x00\xfe\xff\xff", 5); +static void hook_block(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + const uint64_t expected_addresses[] = { + CODE_ADDRESS, + LOOP_ADDRESS, + LOOP_ADDRESS, + }; + BlockState *state = user_data; - err = uc_mem_write(uc, 0x1000000, code, sizeof(code)); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_mem_write\n", count++); - - uc_hook h1; + if (state->count >= 3 || address != expected_addresses[state->count] || + size != 3) { + state->failed = true; + uc_emu_stop(uc); + return; + } + state->count++; + if (state->count == 3) { + if (uc_emu_stop(uc) != UC_ERR_OK) { + state->failed = true; + } + } +} +int main(void) +{ + const uint8_t code[] = { + 0x90, /* nop */ + 0xeb, 0x01, /* jmp LOOP_ADDRESS */ + 0x90, /* unreachable nop */ + 0x90, /* nop */ + 0xeb, 0xfd, /* jmp LOOP_ADDRESS */ + }; + BlockState state = {0}; + uc_engine *uc = NULL; + uc_hook hook; + uc_err err; - err = uc_hook_add(uc, &h1, UC_HOOK_BLOCK, cb_hookblock, NULL, 1, 0); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_hook_add\n", count++); + err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_open failed: %s\n", uc_strerror(err)); + return 1; + } + err = uc_mem_map(uc, CODE_ADDRESS, 0x1000, UC_PROT_ALL); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_map failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_write(uc, CODE_ADDRESS, code, sizeof(code)); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_write failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_hook_add(uc, &hook, UC_HOOK_BLOCK, hook_block, &state, 1, 0); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_hook_add failed: %s\n", uc_strerror(err)); + goto fail; + } - err = uc_emu_start(uc, 0x1000000, 0x1000000 + sizeof(code), 0, 1030); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_emu_start\n", count++); + err = uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_emu_start failed: %s\n", uc_strerror(err)); + goto fail; + } + if (state.failed || state.count != 3) { + fprintf(stderr, "unexpected block trace: count=%u failed=%d\n", + state.count, state.failed); + goto fail; + } - fprintf(stderr, "ok %d - Done", count++); + err = uc_close(uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_close failed: %s\n", uc_strerror(err)); + return 1; + } + return 0; - return 0; +fail: + uc_close(uc); + return 1; } diff --git a/tests/regress/callback-pc.py b/tests/regress/callback_pc.py similarity index 100% rename from tests/regress/callback-pc.py rename to tests/regress/callback_pc.py diff --git a/tests/regress/context_pickle.py b/tests/regress/context_pickle.py new file mode 100644 index 0000000000..831cc788f7 --- /dev/null +++ b/tests/regress/context_pickle.py @@ -0,0 +1,51 @@ +import pickle +import sys + +from unicorn import Uc, UC_ARCH_X86, UC_MODE_64 +from unicorn.x86_const import UC_X86_REG_RAX, UC_X86_REG_XMM0 + + +mu = Uc(UC_ARCH_X86, UC_MODE_64) +context = mu.context_save() +context_state = context.__getstate__() +assert len(context_state[0]) == context_state[1] +context = pickle.loads(pickle.dumps(context)) + +if sys.version_info[0] == 2: + import new + + malformed_context = new.instance(context.__class__) + try: + malformed_context.__setstate__(( + context_state[0][:-1], + context_state[1], + context_state[2], + context_state[3], + )) + except ValueError: + pass + else: + raise AssertionError("Malformed Unicorn context was accepted") + + assert malformed_context._to_free is False + del malformed_context + +rax = 0x0123456789ABCDEF +xmm0 = 0xFFEEDDCCBBAA99887766554433221100 +context.reg_write(UC_X86_REG_RAX, rax) +context.reg_write(UC_X86_REG_XMM0, xmm0) + +assert context.reg_read(UC_X86_REG_RAX) == rax +assert context.reg_read(UC_X86_REG_XMM0) == xmm0 +assert context.reg_read_batch(( + UC_X86_REG_RAX, + UC_X86_REG_XMM0, +)) == (rax, xmm0) + +rax += 1 +xmm0 += 1 +context.reg_write_batch(((UC_X86_REG_RAX, rax), (UC_X86_REG_XMM0, xmm0))) + +mu.context_restore(context) +assert mu.reg_read(UC_X86_REG_RAX) == rax +assert mu.reg_read(UC_X86_REG_XMM0) == xmm0 diff --git a/tests/regress/cross_page_smc.c b/tests/regress/cross_page_smc.c new file mode 100644 index 0000000000..a42e0631ee --- /dev/null +++ b/tests/regress/cross_page_smc.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include + +#include + +#define MAP_ADDRESS UINT64_C(0x1000) +#define TARGET_ADDRESS UINT64_C(0x1ffd) +#define CALLBACK_TARGET_ADDRESS UINT64_C(0x1800) +#define WRITER_ADDRESS UINT64_C(0x3000) + +typedef struct WriteHookState { + uint64_t address; + int64_t value; + int size; + unsigned int count; +} WriteHookState; + +typedef struct CallbackSmcState { + uc_err write_error; + unsigned int count; +} CallbackSmcState; + +static void record_write(uc_engine *uc, uc_mem_type type, uint64_t address, + int size, int64_t value, void *user_data) +{ + WriteHookState *state = user_data; + + (void)uc; + (void)type; + + state->address = address; + state->size = size; + state->value = value; + state->count++; +} + +static void modify_future_page(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + static const uint8_t replacement = 0x4b; + CallbackSmcState *state = user_data; + + (void)address; + (void)size; + + state->count++; + if (state->count == 1) { + state->write_error = uc_mem_write(uc, CALLBACK_TARGET_ADDRESS + 1, + &replacement, sizeof(replacement)); + } +} + +static void report_uc_error(const char *operation, uc_err error) +{ + fprintf(stderr, "%s failed with %u: %s\n", operation, (unsigned)error, + uc_strerror(error)); +} + +static bool test_guest_cross_page_store(void) +{ + static const uint8_t original_code[] = { + 0xb8, 0x44, 0x33, 0x22, 0x11, /* mov eax, 0x11223344 */ + }; + static const uint8_t expected_code[] = { + 0xb8, 0x44, 0x88, 0x77, 0x11, /* mov eax, 0x11778844 */ + }; + static const uint8_t writer_code[] = { + 0x66, 0xc7, 0x05, 0xff, 0x1f, 0x00, 0x00, 0x88, 0x77, + /* mov word ptr [0x1fff], 0x7788 */ + }; + WriteHookState state = {0}; + uc_engine *uc = NULL; + uc_hook hook; + uc_err error; + uint8_t code[sizeof(expected_code)]; + uint32_t eax = 0; + bool failed = false; + + error = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); + if (error != UC_ERR_OK) { + report_uc_error("uc_open(guest SMC)", error); + return false; + } + error = uc_mem_map(uc, MAP_ADDRESS, 0x3000, UC_PROT_ALL); + if (error == UC_ERR_OK) { + error = uc_mem_write(uc, TARGET_ADDRESS, original_code, + sizeof(original_code)); + } + if (error == UC_ERR_OK) { + error = + uc_mem_write(uc, WRITER_ADDRESS, writer_code, sizeof(writer_code)); + } + if (error != UC_ERR_OK) { + report_uc_error("guest SMC setup", error); + failed = true; + goto cleanup; + } + + error = uc_emu_start(uc, TARGET_ADDRESS, + TARGET_ADDRESS + sizeof(original_code), 0, 0); + if (error != UC_ERR_OK) { + report_uc_error("initial target uc_emu_start", error); + failed = true; + goto cleanup; + } + error = uc_hook_add(uc, &hook, UC_HOOK_MEM_WRITE, record_write, &state, + TARGET_ADDRESS + 2, TARGET_ADDRESS + 2); + if (error != UC_ERR_OK) { + report_uc_error("uc_hook_add(write)", error); + failed = true; + goto cleanup; + } + error = uc_emu_start(uc, WRITER_ADDRESS, + WRITER_ADDRESS + sizeof(writer_code), 0, 0); + if (error != UC_ERR_OK) { + report_uc_error("writer uc_emu_start", error); + failed = true; + } + error = uc_mem_read(uc, TARGET_ADDRESS, code, sizeof(code)); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_read(modified code)", error); + failed = true; + } else if (memcmp(code, expected_code, sizeof(code)) != 0) { + fprintf(stderr, "cross-page store wrote unexpected bytes\n"); + failed = true; + } + if (state.count != 1 || state.address != TARGET_ADDRESS + 2 || + state.size != 2 || state.value != 0x7788) { + fprintf(stderr, + "unexpected cross-page hook: count=%u address=0x%" PRIx64 + " size=%d value=0x%" PRIx64 "\n", + state.count, state.address, state.size, (uint64_t)state.value); + failed = true; + } + + eax = 0; + error = uc_reg_write(uc, UC_X86_REG_EAX, &eax); + if (error == UC_ERR_OK) { + error = uc_emu_start(uc, TARGET_ADDRESS, + TARGET_ADDRESS + sizeof(expected_code), 0, 0); + } + if (error == UC_ERR_OK) { + error = uc_reg_read(uc, UC_X86_REG_EAX, &eax); + } + if (error != UC_ERR_OK) { + report_uc_error("modified target execution", error); + failed = true; + } else if (eax != 0x11778844) { + fprintf(stderr, "stale target result: eax=0x%" PRIx32 "\n", eax); + failed = true; + } + +cleanup: + if (uc != NULL) { + error = uc_close(uc); + if (error != UC_ERR_OK) { + report_uc_error("uc_close(guest SMC)", error); + failed = true; + } + } + return !failed; +} + +static bool test_callback_active_tb_write(void) +{ + static const uint8_t code[] = { + 0x40, /* inc eax */ + 0x43, /* inc ebx */ + }; + CallbackSmcState state = {UC_ERR_OK, 0}; + uc_engine *uc = NULL; + uc_hook hook; + uc_tb tb; + uc_err error; + uint8_t modified_byte = 0; + uint32_t eax = 0; + uint32_t ebx = 0; + bool failed = false; + + error = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); + if (error != UC_ERR_OK) { + report_uc_error("uc_open(callback SMC)", error); + return false; + } + error = uc_mem_map(uc, MAP_ADDRESS, 0x2000, UC_PROT_ALL); + if (error == UC_ERR_OK) { + error = uc_mem_write(uc, CALLBACK_TARGET_ADDRESS, code, sizeof(code)); + } + if (error == UC_ERR_OK) { + error = uc_hook_add(uc, &hook, UC_HOOK_CODE, modify_future_page, &state, + CALLBACK_TARGET_ADDRESS, CALLBACK_TARGET_ADDRESS); + } + if (error == UC_ERR_OK) { + error = uc_ctl_request_cache(uc, CALLBACK_TARGET_ADDRESS, &tb); + } + if (error != UC_ERR_OK) { + report_uc_error("callback SMC setup", error); + failed = true; + goto cleanup; + } + if (tb.pc != CALLBACK_TARGET_ADDRESS || tb.size < sizeof(code)) { + fprintf(stderr, + "fixture did not create a two-page TB: pc=0x%" PRIx64 + " size=%u\n", + tb.pc, (unsigned)tb.size); + failed = true; + goto cleanup; + } + + error = uc_emu_start(uc, CALLBACK_TARGET_ADDRESS, + CALLBACK_TARGET_ADDRESS + sizeof(code), 0, 0); + if (error != UC_ERR_OK) { + report_uc_error("callback SMC uc_emu_start", error); + failed = true; + } + error = uc_mem_read(uc, CALLBACK_TARGET_ADDRESS + 1, &modified_byte, + sizeof(modified_byte)); + if (error == UC_ERR_OK) { + error = uc_reg_read(uc, UC_X86_REG_EAX, &eax); + } + if (error == UC_ERR_OK) { + error = uc_reg_read(uc, UC_X86_REG_EBX, &ebx); + } + if (error != UC_ERR_OK) { + report_uc_error("callback SMC verification", error); + failed = true; + } else if (state.write_error != UC_ERR_OK || state.count != 2 || + modified_byte != 0x4b || eax != 1 || ebx != UINT32_MAX) { + fprintf(stderr, + "stale future-page execution: write_error=%u count=%u " + "byte=0x%02x eax=0x%" PRIx32 " ebx=0x%" PRIx32 "\n", + (unsigned)state.write_error, state.count, + (unsigned)modified_byte, eax, ebx); + failed = true; + } + +cleanup: + if (uc != NULL) { + error = uc_close(uc); + if (error != UC_ERR_OK) { + report_uc_error("uc_close(callback SMC)", error); + failed = true; + } + } + return !failed; +} + +int main(void) +{ + bool guest_store_ok = test_guest_cross_page_store(); + bool callback_write_ok = test_callback_active_tb_write(); + + return guest_store_ok && callback_write_ok ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/regress/eflags_noset.c b/tests/regress/eflags_noset.c index e3a105bd00..c661371126 100644 --- a/tests/regress/eflags_noset.c +++ b/tests/regress/eflags_noset.c @@ -1,126 +1,96 @@ -#include +#include #include -#include #include -#define X86_CODE32 "\x9C\x68\xFF\xFE\xFF\xFF\x9D\x9C\x58\x9D" // pushf; push ffffffeff; popf; pushf; pop eax; popf -#define ADDRESS 0x1000000 -#define PAGE_8K (1 << 13) -#define PAGE_4K (1 << 12) -#define TARGET_PAGE_MASK ~(PAGE_4K - 1) -#define TARGET_PAGE_PREPARE(addr) (((addr) + PAGE_4K - 1) & TARGET_PAGE_MASK) -#define TARGET_PAGE_ALIGN(addr) (addr - (TARGET_PAGE_PREPARE(addr) - addr) & TARGET_PAGE_MASK) +#define CODE_ADDRESS UINT64_C(0x1000000) +#define INITIAL_EFLAGS UINT32_C(0x00000206) +#define POPPED_EFLAGS UINT32_C(0x00247ed7) -#if defined(__i386__) -typedef uint32_t puint; -#define PRIX3264 PRIX32 -#else -typedef uint64_t puint; -#define PRIX3264 PRIX64 -#endif - -uint32_t realEflags(void) +int main(void) { - puint val = 0; - -#if defined(__i386__) - puint i = 0xFFFFFEFF; //attempt to set ALL bits except trap flag. - - __asm__("pushf\n\t" - "push %0\n\t" - "popf\n\t" - "pushf\n\t" - "pop %0\n\t" - "popf" - : "=r"(val) - : "r"(i) - : "%0"); -#elif defined(__x86_64__) - puint i = 0xFFFFFEFF; //attempt to set ALL bits except trap flag. - - __asm__("pushfq\n\t" - "pushq %0\n\t" - "popfq\n\t" - "pushfq\n\t" - "popq %0\n\t" - "popfq" - : "=r"(val) - : "r"(i) - : "%0"); -#endif - - printf("Real system eflags: 0x%08"PRIX3264"\n", val); - - return (uint32_t)val & 0xFFFFFFFF; -} - -static void VM_exec(void) -{ -#if defined(__i386__) || defined(__x86_64__) - uc_engine *uc; + const uint8_t code[] = { + 0x9c, /* pushfd */ + 0x68, 0xff, 0xfe, 0xff, 0xff, /* push 0xfffffeff */ + 0x9d, /* popfd */ + 0x9c, /* pushfd */ + 0x58, /* pop eax */ + 0x9d, /* popfd */ + }; + uint32_t eax = 0; + uint32_t eflags = INITIAL_EFLAGS; + uint32_t esp = (uint32_t)CODE_ADDRESS + 0x800; + uint32_t final_esp = 0; + uc_engine *uc = NULL; uc_err err; - unsigned int r_eax, eflags, r_esp, realflags = 0; - - r_eax = 0; - r_esp = ADDRESS+0x100; //some safe distance from main code. - eflags = 0x00000206; - // Initialize emulator in X86-32bit mode err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); - if(err) - { - printf("Failed on uc_open() with error returned: %s\n", uc_strerror(err)); - return; + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_open failed: %s\n", uc_strerror(err)); + return 1; } - - err = uc_mem_map(uc, ADDRESS, (2 * 1024 * 1024), UC_PROT_ALL); - if(err != UC_ERR_OK) - { - printf("Failed to map memory %s\n", uc_strerror(err)); - return; + err = uc_ctl_set_cpu_model(uc, UC_CPU_X86_QEMU64); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_ctl_set_cpu_model failed: %s\n", uc_strerror(err)); + goto fail; } - - // write machine code to be emulated to memory - err = uc_mem_write(uc, ADDRESS, X86_CODE32, sizeof(X86_CODE32) - 1); - if(err != UC_ERR_OK) - { - printf("Failed to write emulation code to memory, quit!: %s(len %lu)\n", uc_strerror(err), (unsigned long)sizeof(X86_CODE32) - 1); - return; + err = uc_mem_map(uc, CODE_ADDRESS, 0x1000, UC_PROT_ALL); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_map failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_write(uc, CODE_ADDRESS, code, sizeof(code)); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_write failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_reg_write(uc, UC_X86_REG_ESP, &esp); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_write(ESP) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_reg_write(uc, UC_X86_REG_EFLAGS, &eflags); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_write(EFLAGS) failed: %s\n", uc_strerror(err)); + goto fail; } - // initialize machine registers - uc_reg_write(uc, UC_X86_REG_EAX, &r_eax); - uc_reg_write(uc, UC_X86_REG_ESP, &r_esp); //make stack pointer point to already mapped memory so we don't need to hook. - uc_reg_write(uc, UC_X86_REG_EFLAGS, &eflags); - - // emulate machine code in infinite time - err = uc_emu_start(uc, ADDRESS, ADDRESS + (sizeof(X86_CODE32) - 1), 0, 0); - if(err) - { - printf("Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - - uc_close(uc); - return; + err = uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_emu_start failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_reg_read(uc, UC_X86_REG_EAX, &eax); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_read(EAX) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_reg_read(uc, UC_X86_REG_EFLAGS, &eflags); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_read(EFLAGS) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_reg_read(uc, UC_X86_REG_ESP, &final_esp); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_read(ESP) failed: %s\n", uc_strerror(err)); + goto fail; + } + if (eax != POPPED_EFLAGS || eflags != INITIAL_EFLAGS || final_esp != esp) { + fprintf(stderr, + "unexpected flags state: EAX=0x%08" PRIx32 + " EFLAGS=0x%08" PRIx32 " ESP=0x%08" PRIx32 "\n", + eax, eflags, final_esp); + goto fail; } - uc_reg_read(uc, UC_X86_REG_EAX, &r_eax); - uc_reg_read(uc, UC_X86_REG_EFLAGS, &eflags); + err = uc_close(uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_close failed: %s\n", uc_strerror(err)); + return 1; + } + return 0; +fail: uc_close(uc); - - printf(">>> Emulation done. Below is the CPU context\n"); - printf(">>> EAX = 0x%08X\n", r_eax); - printf(">>> EFLAGS = 0x%08X\n", eflags); - - realflags = realEflags(); - - assert(r_eax == realflags); -#endif -} - -int main(int argc, char *argv[]) -{ - VM_exec(); - return 0; + return 1; } diff --git a/tests/regress/eflags_nosync.c b/tests/regress/eflags_nosync.c index 398608e1e8..84464f39cc 100644 --- a/tests/regress/eflags_nosync.c +++ b/tests/regress/eflags_nosync.c @@ -1,182 +1,59 @@ -#include -#include -#include - #include -#define X86_CODE32 "\x33\xD2\x8A\xD4\x8B\xC8\x81\xE1\xFF\x00\x00\x00" // XOR edx,edx; MOV dl,ah; MOV ecx,eax; AND ecx,FF #define ADDRESS 0x1000000 -#define PAGE_8K (1 << 13) -#define PAGE_4K (1 << 12) -#define TARGET_PAGE_MASK ~(PAGE_4K - 1) -#define TARGET_PAGE_PREPARE(addr) (((addr) + PAGE_4K - 1) & TARGET_PAGE_MASK) -#define TARGET_PAGE_ALIGN(addr) ((addr - (TARGET_PAGE_PREPARE(addr) - addr)) & TARGET_PAGE_MASK) -static uint64_t instructions = 0; +static unsigned int instruction_count; -static void hook_ins(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) +static void count_instruction(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) { - instructions++; -} - -static bool hook_invalid_mem(uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, void *user_data) -{ - uc_err err; - uint64_t address_align = TARGET_PAGE_ALIGN(address); - - if(address == 0) - { - printf("Address is 0, proof 0x%" PRIx64 "\n", address); - return false; - } - - switch(type) - { - default: - return false; - break; - case UC_MEM_WRITE_UNMAPPED: - printf("Mapping write address 0x%" PRIx64 " to aligned 0x%" PRIx64 "\n", address, address_align); - - err = uc_mem_map(uc, address_align, PAGE_8K, UC_PROT_ALL); - if(err != UC_ERR_OK) - { - printf("Failed to map memory on UC_MEM_WRITE_UNMAPPED %s\n", uc_strerror(err)); - return false; - } - - return true; - break; - case UC_MEM_READ_UNMAPPED: - - printf("Mapping read address 0x%" PRIx64 " to aligned 0x%" PRIx64 "\n", address, address_align); - - - err = uc_mem_map(uc, address_align, PAGE_8K, UC_PROT_ALL); - if(err != UC_ERR_OK) - { - printf("Failed to map memory on UC_MEM_READ_UNMAPPED %s\n", uc_strerror(err)); - return false; - } - - return true; - break; - } + (void)uc; + (void)address; + (void)size; + (void)user_data; + instruction_count++; } -static void VM_exec(void) +int main(void) { + const uint8_t code[] = { + 0x33, 0xd2, /* xor edx, edx */ + 0x8a, 0xd4, /* mov dl, ah */ + 0x8b, 0xc8, /* mov ecx, eax */ + 0x81, 0xe1, 0xff, 0x00, 0x00, 0x00, /* and ecx, 0xff */ + }; + uint32_t eax = 0x1db10106; + uint32_t ebx = 0x7efde000; + uint32_t ecx = 0x7efde000; + uint32_t edx = 0x1db1; + uint32_t eflags = 0x206; uc_engine *uc; - uc_err err; - uc_hook trace1, trace2; - unsigned int r_eax, r_ebx, r_ecx, r_edx, r_ebp, r_esp, r_esi, r_edi, r_eip, eflags; - unsigned int tr_eax, tr_ebx, tr_ecx, tr_edx, tr_ebp, tr_esp, tr_esi, tr_edi, tr_eip, t_eflags; - - - r_eax = tr_eax = 0x1DB10106; - r_ebx = tr_ebx = 0x7EFDE000; - r_ecx = tr_ecx = 0x7EFDE000; - r_edx = tr_edx = 0x00001DB1; - r_ebp = tr_ebp = 0x0018FF88; - r_esp = tr_esp = 0x0018FF14; - r_esi = tr_esi = 0x0; - r_edi = tr_edi = 0x0; - r_eip = tr_eip = 0x004939F3; - t_eflags = eflags = 0x00000206; - - // Initialize emulator in X86-32bit mode - err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); - if(err) - { - printf("Failed on uc_open() with error returned: %s", uc_strerror(err)); - return; - } + uc_hook hook; - err = uc_mem_map(uc, ADDRESS, (4 * 1024 * 1024), UC_PROT_ALL); - if(err != UC_ERR_OK) - { - printf("Failed to map memory %s", uc_strerror(err)); - return; + if (uc_open(UC_ARCH_X86, UC_MODE_32, &uc) != UC_ERR_OK) { + return 1; } - - // write machine code to be emulated to memory - err = uc_mem_write(uc, ADDRESS, X86_CODE32, sizeof(X86_CODE32) - 1); - if(err != UC_ERR_OK) - { - printf("Failed to write emulation code to memory, quit!: %s(len %zu)", uc_strerror(err), sizeof(X86_CODE32) - 1); - return; - } - - // initialize machine registers - uc_reg_write(uc, UC_X86_REG_EAX, &r_eax); - uc_reg_write(uc, UC_X86_REG_EBX, &r_ebx); - uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx); - uc_reg_write(uc, UC_X86_REG_EDX, &r_edx); - uc_reg_write(uc, UC_X86_REG_EBP, &r_ebp); - uc_reg_write(uc, UC_X86_REG_ESP, &r_esp); - uc_reg_write(uc, UC_X86_REG_ESI, &r_esi); - uc_reg_write(uc, UC_X86_REG_EDI, &r_edi); - uc_reg_write(uc, UC_X86_REG_EFLAGS, &eflags); - - uc_hook_add(uc, &trace1, UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED, (void *)hook_invalid_mem, NULL, 1, 0); - - // tracing all instruction by having @begin > @end - uc_hook_add(uc, &trace2, UC_HOOK_CODE, (void *)hook_ins, NULL, 1, 0); - - // emulate machine code in infinite time - err = uc_emu_start(uc, ADDRESS, ADDRESS + (sizeof(X86_CODE32) - 1), 0, 0); - if(err) - { - printf("Failed on uc_emu_start() with error returned %u: %s", err, uc_strerror(err)); - instructions = 0; - + if (uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_EAX, &eax) != UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_EBX, &ebx) != UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_ECX, &ecx) != UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_EDX, &edx) != UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_EFLAGS, &eflags) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_CODE, count_instruction, NULL, 1, 0) != + UC_ERR_OK || + uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0) != UC_ERR_OK || + uc_reg_read(uc, UC_X86_REG_ECX, &ecx) != UC_ERR_OK || + uc_reg_read(uc, UC_X86_REG_EDX, &edx) != UC_ERR_OK || + uc_reg_read(uc, UC_X86_REG_EFLAGS, &eflags) != UC_ERR_OK) { uc_close(uc); - return; + return 1; } - uc_reg_read(uc, UC_X86_REG_EAX, &r_eax); - uc_reg_read(uc, UC_X86_REG_EBX, &r_ebx); - uc_reg_read(uc, UC_X86_REG_ECX, &r_ecx); - uc_reg_read(uc, UC_X86_REG_EDX, &r_edx); - uc_reg_read(uc, UC_X86_REG_EBP, &r_ebp); - uc_reg_read(uc, UC_X86_REG_ESP, &r_esp); - uc_reg_read(uc, UC_X86_REG_ESI, &r_esi); - uc_reg_read(uc, UC_X86_REG_EDI, &r_edi); - uc_reg_read(uc, UC_X86_REG_EIP, &r_eip); - uc_reg_read(uc, UC_X86_REG_EFLAGS, &eflags); - - uc_close(uc); - - printf(">>> Emulation done. Below is the CPU context\n"); - printf(">>> EAX = 0x%08X %s\n", r_eax, (r_eax == tr_eax ? "" : "(m)")); - printf(">>> EBX = 0x%08X %s\n", r_ebx, (r_ebx == tr_ebx ? "" : "(m)")); - printf(">>> ECX = 0x%08X %s\n", r_ecx, (r_ecx == tr_ecx ? "" : "(m)")); - printf(">>> EDX = 0x%08X %s\n", r_edx, (r_edx == tr_edx ? "" : "(m)")); - printf(">>> EBP = 0x%08X %s\n", r_ebp, (r_ebp == tr_ebp ? "" : "(m)")); - printf(">>> ESP = 0x%08X %s\n", r_esp, (r_esp == tr_esp ? "" : "(m)")); - printf(">>> ESI = 0x%08X %s\n", r_esi, (r_esi == tr_esi ? "" : "(m)")); - printf(">>> EDI = 0x%08X %s\n", r_edi, (r_edi == tr_edi ? "" : "(m)")); - printf(">>> EIP = 0x%08X %s\n", (r_eip - ADDRESS) + tr_eip, (r_eip == tr_eip ? "" : "(m)\n")); - printf(">>> EFLAGS = 0x%08X %s\n", eflags, (eflags == t_eflags ? "" : "(m)")); - - printf(">>> Instructions executed %" PRIu64 "\n", instructions); - - assert(r_eax == 0x1DB10106); - assert(r_ebx == 0x7EFDE000); - assert(r_ecx == 0x00000006); - assert(r_edx == 0x00000001); - assert(r_ebp == 0x0018FF88); - assert(r_esp == 0x0018FF14); - assert(r_esi == 0x00000000); - assert(r_edi == 0x00000000); - assert(eflags == 0x00000206); //we shouldn't fail this assert, eflags should be 0x00000206 because the last AND instruction produces a non-zero result. - - instructions = 0; -} - - -int main(int argc, char *argv[]) -{ - VM_exec(); - return 0; + if (uc_close(uc) != UC_ERR_OK) { + return 1; + } + return ecx == 6 && edx == 1 && eflags == 0x206 && instruction_count == 4 + ? 0 + : 1; } diff --git a/tests/regress/emu_clear_errors.c b/tests/regress/emu_clear_errors.c index 3dda86a9db..fe4529a681 100644 --- a/tests/regress/emu_clear_errors.c +++ b/tests/regress/emu_clear_errors.c @@ -1,151 +1,58 @@ -#include -#include -#include - #include -static int count = 1; - -bool cb_hookunmapped(uc_engine *uc, uc_mem_type type, uint64_t address, uint32_t size, int64_t value, void *user_data) { - uint32_t pc = 0; - uc_reg_read(uc, UC_X86_REG_EIP, &pc); - fprintf(stderr, "mem unmapped: 0x%x type: %x address: 0x%"PRIx64" length: %x value: 0x%"PRIx64"\n", - pc, type, address, size, value); - - uc_err err = UC_ERR_OK; - err = uc_emu_stop(uc); - if (err != UC_ERR_OK) { - fprintf(stderr, "stop not ok"); - exit(0); - } - return true; +#define ADDRESS 0x1000 + +typedef struct UnmappedState { + unsigned int count; + uc_err stop_error; +} UnmappedState; + +static bool stop_on_unmapped(uc_engine *uc, uc_mem_type type, uint64_t address, + int size, int64_t value, void *user_data) +{ + UnmappedState *state = user_data; + + (void)type; + (void)address; + (void)size; + (void)value; + state->count++; + state->stop_error = uc_emu_stop(uc); + return true; } -// move esi, dword ptr [ecx + eax + 0x28] -// add esi, eax -// lea eax, dword ptr [ebp - 4] -// push eax -// push 0x40 -// push 0x10 -// push esi -// call some address -#define CODE "\x8B\x74\x01\x28" \ - "\x0C\xF0" \ - "\x8D\x45\xFC" \ - "\x50" \ - "\x6A\x40" \ - "\x6A\x10" \ - "\x56" \ - "\xFF\x15\x20\x20\x00\x10" - -int main(void) { - uc_engine *uc; - - uc_err err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_open\n", count++); - - err = uc_mem_map(uc, 0x1000, 0x1000, UC_PROT_ALL); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_mem_map: code\n", count++); - - uint8_t code[0x1000]; - memset(code, 0x0, sizeof(code)); - memcpy(code, CODE, sizeof(CODE)); - - err = uc_mem_write(uc, 0x1000, code, sizeof(code)); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_mem_write: code\n", count++); - - uint32_t eip = 0x1000; - err = uc_reg_write(uc, UC_X86_REG_EIP, &eip); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_reg_write: eip\n", count++); - - err = uc_mem_map(uc, 0x4000, 0x4000, UC_PROT_ALL); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_mem_map: stack\n", count++); - - uint8_t stack[0x4000]; - memset(stack, 0x0, sizeof(stack)); - - err = uc_mem_write(uc, 0x4000, code, sizeof(code)); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_mem_write: stack\n", count++); - - uint32_t esp = 0x6000; - err = uc_reg_write(uc, UC_X86_REG_ESP, &esp); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_reg_write: esp\n", count++); - - uint32_t ebp = 0x6000; - err = uc_reg_write(uc, UC_X86_REG_EBP, &ebp); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_reg_write: ebp\n", count++); - - uc_hook h1; - - err = uc_hook_add(uc, &h1, UC_HOOK_MEM_UNMAPPED, cb_hookunmapped, NULL, 1, 0); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_hook_add\n", count++); - - // this should execute only a single instruction at 0x1000, because - // that instruction accesses invalid memory. - err = uc_emu_start(uc, 0x1000, 0x100F, 0, 0); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_emu_start\n", count++); - - // yes, not necessary, but to demonstrate the UC API is working as expected - eip = 0x1004; - err = uc_reg_write(uc, UC_X86_REG_EIP, &eip); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_reg_write: eip\n", count++); - - // this should execute the remaining instructions up to (but not includign) 0x100F. - // currently, it returns an error about an unmapped read. - // seems that this error should have been returned in the previous call - // to emu_start. - err = uc_emu_start(uc, 0x1004, 0x100F, 0, 0); - if (err != UC_ERR_OK) { - fprintf(stderr, "not ok %d - %s\n", count++, uc_strerror(err)); - exit(0); - } - fprintf(stderr, "ok %d - uc_emu_start\n", count++); - - fprintf(stderr, "ok %d - Done", count++); - - return 0; +int main(void) +{ + /* mov eax, [0]; nop */ + const uint8_t code[] = {0xa1, 0x00, 0x00, 0x00, 0x00, 0x90}; + UnmappedState state = {0}; + uc_engine *uc; + uc_hook hook; + uc_err err; + + err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); + if (err != UC_ERR_OK) { + return 1; + } + if (uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_MEM_UNMAPPED, stop_on_unmapped, &state, + 1, 0) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_OK || state.count != 1 || state.stop_error != UC_ERR_OK) { + uc_close(uc); + return 1; + } + + err = uc_emu_start(uc, ADDRESS + 5, ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_OK || state.count != 1) { + uc_close(uc); + return 1; + } + + return uc_close(uc) == UC_ERR_OK ? 0 : 1; } diff --git a/tests/regress/emu_stop_in_hook_overrun.c b/tests/regress/emu_stop_in_hook_overrun.c index 3783ffae8b..e4edb68667 100644 --- a/tests/regress/emu_stop_in_hook_overrun.c +++ b/tests/regress/emu_stop_in_hook_overrun.c @@ -1,116 +1,49 @@ -/* -Test for uc_emu_stop() in code hook not always stopping the emu at the current instruction. -(Sometimes it will execute and stop at the next instruction). -*/ - -// windows specific -#ifdef _MSC_VER -#include -#include -#include -#define PRIx64 "llX" -#include -#ifdef _WIN64 -#pragma comment(lib, "unicorn_staload64.lib") -#else // _WIN64 -#pragma comment(lib, "unicorn_staload.lib") -#endif // _WIN64 - -// posix specific -#else // _MSC_VER #include -#include "pthread.h" -#endif // _MSC_VER - -// common includes -#include +#define ADDRESS 0x100000 +#define STOP_ADDRESS (ADDRESS + 8) -// Test MIPS little endian code. -// This should loop forever. -const uint64_t addr = 0x100000; -const unsigned char test_code[] = { - 0x00,0x00,0x00,0x00, // 100000: nop - 0x00,0x00,0x00,0x00, // 100004: nop - 0x00,0x00,0x00,0x00, // 100008: nop - 0x00,0x00,0x00,0x00, // 10000C: nop -}; -bool test_passed_ok = false; +static uc_err stop_error; - -// This hook is used to show that code is executing in the emulator. -static void mips_codehook(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) +static void stop_at_instruction(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) { - printf("Executing: %"PRIx64"\n", address); - if( address == 0x100008 ) - { - printf("Stopping at: %"PRIx64"\n", address); - uc_emu_stop(uc); + (void)size; + (void)user_data; + if (address == STOP_ADDRESS) { + stop_error = uc_emu_stop(uc); } } - -int main(int argc, char **argv, char **envp) +int main(void) { + const uint8_t code[16] = {0}; + uint32_t pc = 0; uc_engine *uc; + uc_hook hook; uc_err err; - uc_hook hhc; - uint32_t val; - // Initialize emulator in MIPS 32bit little endian mode - printf("uc_open()\n"); err = uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32, &uc); - if (err) - { - printf("Failed on uc_open() with error returned: %u\n", err); - return err; + if (err != UC_ERR_OK) { + return 1; } - - // map in a page of mem - printf("uc_mem_map()\n"); - err = uc_mem_map(uc, addr, 0x1000, UC_PROT_ALL); - if (err) - { - printf("Failed on uc_mem_map() with error returned: %u\n", err); - return err; + if (uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_CODE, stop_at_instruction, NULL, 1, 0) != + UC_ERR_OK) { + uc_close(uc); + return 1; } - // write machine code to be emulated to memory - printf("uc_mem_write()\n"); - err = uc_mem_write(uc, addr, test_code, sizeof(test_code)); - if( err ) - { - printf("Failed on uc_mem_write() with error returned: %u\n", err); - return err; + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_OK || stop_error != UC_ERR_OK || + uc_reg_read(uc, UC_MIPS_REG_PC, &pc) != UC_ERR_OK) { + uc_close(uc); + return 1; } - // hook all instructions by having @begin > @end - printf("uc_hook_add()\n"); - uc_hook_add(uc, &hhc, UC_HOOK_CODE, mips_codehook, NULL, 1, 0); - if( err ) - { - printf("Failed on uc_hook_add(code) with error returned: %u\n", err); - return err; + if (uc_close(uc) != UC_ERR_OK) { + return 1; } - - // start executing code - printf("uc_emu_start()\n"); - uc_emu_start(uc, addr, addr+sizeof(test_code), 0, 0); - - - // done executing, print some reg values as a test - uc_reg_read(uc, UC_MIPS_REG_PC, &val); printf("pc is %X\n", val); - test_passed_ok = val == 0x100008; - - // free resources - printf("uc_close()\n"); - uc_close(uc); - - if( test_passed_ok ) - printf("\n\nTEST PASSED!\n\n"); - else - printf("\n\nTEST FAILED!\n\n"); - - return 0; + return pc == STOP_ADDRESS ? 0 : 1; } - diff --git a/tests/regress/exit_pending_order.c b/tests/regress/exit_pending_order.c new file mode 100644 index 0000000000..5bc55864c9 --- /dev/null +++ b/tests/regress/exit_pending_order.c @@ -0,0 +1,276 @@ +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include + +#define EXIT_ORDER_STRESS_ITERATIONS 64 +#define EXIT_ORDER_MAX_ATTEMPTS (EXIT_ORDER_STRESS_ITERATIONS * 4) +#define EXIT_ORDER_TIMEOUT_US 10000 +#define EXIT_ORDER_WAIT_MS 10000 + +static const uint64_t code_address = 0x100000; +static const uint8_t code[] = { 0x40 }; /* inc eax */ + +typedef enum ExitOrder { + EXIT_ORDER_COUNT_FIRST, + EXIT_ORDER_TIMEOUT_FIRST, +} ExitOrder; + +typedef struct ExitOrderContext { + uc_err query_error; + size_t timed_out; + uint32_t calls; + bool wait_for_timeout; +} ExitOrderContext; + +static void sleep_one_millisecond(void) +{ +#ifdef _WIN32 + Sleep(1); +#else + usleep(1000); +#endif +} + +static void exit_order_callback(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + ExitOrderContext *context = user_data; + unsigned int elapsed; + + (void)address; + (void)size; + + context->calls++; + if (!context->wait_for_timeout) { + return; + } + + for (elapsed = 0; elapsed < EXIT_ORDER_WAIT_MS; elapsed++) { + context->query_error = + uc_query(uc, UC_QUERY_TIMEOUT, &context->timed_out); + if (context->query_error != UC_ERR_OK || context->timed_out) { + break; + } + sleep_one_millisecond(); + } + if (context->query_error == UC_ERR_OK && context->timed_out) { + /* timed_out is payload; let the timer publish its pending edge. */ + sleep_one_millisecond(); + } +} + +static void report_uc_error(const char *operation, uc_err error, + ExitOrder order, unsigned int iteration) +{ + fprintf(stderr, "%s failed for order %u at iteration %u with %u: %s\n", + operation, (unsigned int)order, iteration + 1, + (unsigned int)error, uc_strerror(error)); +} + +static bool verify_registers(uc_engine *uc, ExitOrder order, + unsigned int iteration, uint32_t expected_eip, + uint32_t expected_eax) +{ + uint32_t eip = 0; + uint32_t eax = 0; + uc_err error; + + error = uc_reg_read(uc, UC_X86_REG_EIP, &eip); + if (error != UC_ERR_OK) { + report_uc_error("uc_reg_read(EIP)", error, order, iteration); + return false; + } + error = uc_reg_read(uc, UC_X86_REG_EAX, &eax); + if (error != UC_ERR_OK) { + report_uc_error("uc_reg_read(EAX)", error, order, iteration); + return false; + } + if (eip != expected_eip || eax != expected_eax) { + fprintf(stderr, + "unexpected state for order %u at iteration %u: " + "eip=0x%08" PRIx32 " eax=%" PRIu32 "\n", + (unsigned int)order, iteration + 1, eip, eax); + return false; + } + return true; +} + +static bool run_order_stress(ExitOrder order, unsigned int *attempts_out) +{ + ExitOrderContext context = {0}; + uc_engine *uc = NULL; + uc_hook hook; + uc_err error; + uint32_t eax; + unsigned int attempts = 0; + unsigned int qualified_iterations = 0; + bool passed = true; + + error = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); + if (error != UC_ERR_OK) { + report_uc_error("uc_open", error, order, 0); + return false; + } + error = uc_mem_map(uc, code_address, 0x1000, UC_PROT_ALL); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_map", error, order, 0); + passed = false; + goto cleanup; + } + error = uc_mem_write(uc, code_address, code, sizeof(code)); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_write", error, order, 0); + passed = false; + goto cleanup; + } + error = uc_hook_add(uc, &hook, + order == EXIT_ORDER_COUNT_FIRST ? UC_HOOK_CODE : + UC_HOOK_BLOCK, + exit_order_callback, &context, code_address, + code_address); + if (error != UC_ERR_OK) { + report_uc_error("uc_hook_add", error, order, 0); + passed = false; + goto cleanup; + } + + /* Pretranslate the counted TB before starting the timing stress. */ + eax = 0; + error = uc_reg_write(uc, UC_X86_REG_EAX, &eax); + if (error == UC_ERR_OK) { + error = uc_emu_start(uc, code_address, code_address + sizeof(code), + 0, 1); + } + if (error != UC_ERR_OK || context.calls != 1 || + !verify_registers(uc, order, 0, code_address + sizeof(code), 1)) { + if (error != UC_ERR_OK) { + report_uc_error("pretranslation", error, order, 0); + } + passed = false; + goto cleanup; + } + + while (qualified_iterations < EXIT_ORDER_STRESS_ITERATIONS && + attempts < EXIT_ORDER_MAX_ATTEMPTS) { + size_t timed_out = 0; + unsigned int iteration = qualified_iterations; + + attempts++; + eax = 0; + context.query_error = UC_ERR_OK; + context.timed_out = 0; + context.calls = 0; + context.wait_for_timeout = true; + error = uc_reg_write(uc, UC_X86_REG_EAX, &eax); + if (error == UC_ERR_OK) { + error = uc_emu_start(uc, code_address, + code_address + sizeof(code), + EXIT_ORDER_TIMEOUT_US, 1); + } + if (error != UC_ERR_OK) { + report_uc_error("timed uc_emu_start", error, order, iteration); + passed = false; + break; + } + if (context.query_error != UC_ERR_OK) { + report_uc_error("callback uc_query", context.query_error, order, + iteration); + passed = false; + break; + } + error = uc_query(uc, UC_QUERY_TIMEOUT, &timed_out); + if (error != UC_ERR_OK) { + report_uc_error("uc_query", error, order, iteration); + passed = false; + break; + } + if (context.calls == 0 && timed_out == 1) { + /* The timeout fired before either ordering edge was exercised. */ + if (!verify_registers(uc, order, iteration, code_address, 0)) { + passed = false; + break; + } + continue; + } + if (context.calls != 1 || context.timed_out != 1 || timed_out != 1 || + !verify_registers(uc, order, iteration, code_address, 0)) { + fprintf(stderr, + "timed order %u failed at iteration %u: calls=%u " + "callback_timeout=%zu timeout=%zu\n", + (unsigned int)order, iteration + 1, context.calls, + context.timed_out, timed_out); + passed = false; + break; + } + + context.calls = 0; + context.wait_for_timeout = false; + error = uc_emu_start(uc, code_address, code_address + sizeof(code), + 0, 1); + if (error != UC_ERR_OK) { + report_uc_error("reuse uc_emu_start", error, order, iteration); + passed = false; + break; + } + error = uc_query(uc, UC_QUERY_TIMEOUT, &timed_out); + if (error != UC_ERR_OK || timed_out != 0 || context.calls != 1 || + !verify_registers(uc, order, iteration, + code_address + sizeof(code), 1)) { + if (error != UC_ERR_OK) { + report_uc_error("reuse uc_query", error, order, iteration); + } + fprintf(stderr, + "reuse order %u failed at iteration %u: calls=%u " + "timeout=%zu\n", + (unsigned int)order, iteration + 1, context.calls, + timed_out); + passed = false; + break; + } + qualified_iterations++; + } + if (passed && qualified_iterations != EXIT_ORDER_STRESS_ITERATIONS) { + fprintf(stderr, + "timed order %u exhausted %u attempts after %u qualified " + "iterations\n", + (unsigned int)order, attempts, qualified_iterations); + passed = false; + } + +cleanup: + error = uc_close(uc); + if (error != UC_ERR_OK) { + report_uc_error("uc_close", error, order, 0); + passed = false; + } + *attempts_out = attempts; + return passed; +} + +int main(void) +{ + unsigned int count_first_attempts = 0; + unsigned int timeout_first_attempts = 0; + bool passed = run_order_stress(EXIT_ORDER_COUNT_FIRST, + &count_first_attempts); + + passed = run_order_stress(EXIT_ORDER_TIMEOUT_FIRST, + &timeout_first_attempts) && passed; + if (passed) { + printf("exit pending order stress passed: %u count-first in %u " + "attempts, %u timeout-first in %u attempts\n", + EXIT_ORDER_STRESS_ITERATIONS, count_first_attempts, + EXIT_ORDER_STRESS_ITERATIONS, timeout_first_attempts); + } + return passed ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/regress/hook_extrainvoke.c b/tests/regress/hook_extrainvoke.c index 54197459ad..b12ccabd09 100644 --- a/tests/regress/hook_extrainvoke.c +++ b/tests/regress/hook_extrainvoke.c @@ -1,93 +1,64 @@ -#include -#include - #include -#define X86_CODE32 "\xf3\xab" // rep stosd dword ptr es:[edi], eax -> Fill (E)CX doublewords at ES:[(E)DI] with EAX #define ADDRESS 0x1000000 -#define ECX_OPS 2 -static long unsigned int hook_called = 0; +#define ITERATIONS 2 + +static unsigned int hook_count; -void hook_ins(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) +static void count_instruction(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) { - hook_called++; - printf("hook called\n"); + (void)uc; + (void)address; + (void)size; + (void)user_data; + hook_count++; } -static void VM_exec(void) +int main(void) { + /* rep stosd */ + const uint8_t code[] = {0xf3, 0xab}; + const uint32_t value = 0xbaadbabe; + uint32_t destination = ADDRESS + 0x300; + uint32_t count = ITERATIONS; + uint32_t result[ITERATIONS] = {0}; uc_engine *uc; + uc_hook hook; uc_err err; - uc_hook trace; - unsigned int r_eax, eflags, r_esp, r_edi, r_ecx; - - r_eax = 0xbaadbabe; - r_esp = ADDRESS+0x20; - r_edi = ADDRESS+0x300; //some safe distance from main code. - eflags = 0x00000206; - r_ecx = ECX_OPS; - // Initialize emulator in X86-32bit mode err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); - if(err) - { - printf("Failed on uc_open() with error returned: %s\n", uc_strerror(err)); - return; - } - - err = uc_mem_map(uc, ADDRESS, (2 * 1024 * 1024), UC_PROT_ALL); - if(err != UC_ERR_OK) - { - printf("Failed to map memory %s\n", uc_strerror(err)); - return; + if (err != UC_ERR_OK) { + return 1; } - - // write machine code to be emulated to memory - err = uc_mem_write(uc, ADDRESS, X86_CODE32, sizeof(X86_CODE32) - 1); - if(err != UC_ERR_OK) - { - printf("Failed to write emulation code to memory, quit!: %s(len %lu)\n", uc_strerror(err), (unsigned long)sizeof(X86_CODE32) - 1); - return; + if (uc_mem_map(uc, ADDRESS, 0x200000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_EAX, &value) != UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_EDI, &destination) != UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_ECX, &count) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_CODE, count_instruction, NULL, 1, 0) != + UC_ERR_OK) { + uc_close(uc); + return 1; } - // initialize machine registers - uc_reg_write(uc, UC_X86_REG_EAX, &r_eax); - uc_reg_write(uc, UC_X86_REG_EDI, &r_edi); - uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx); - uc_reg_write(uc, UC_X86_REG_ESP, &r_esp); //make stack pointer point to already mapped memory so we don't need to hook. - uc_reg_write(uc, UC_X86_REG_EFLAGS, &eflags); - - uc_hook_add(uc, &trace, UC_HOOK_CODE, (void *)hook_ins, NULL, 1, 0); - - // emulate machine code in infinite time - err = uc_emu_start(uc, ADDRESS, ADDRESS + (sizeof(X86_CODE32) - 1), 0, 0); - if(err) - { - printf("Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_OK || + uc_reg_read(uc, UC_X86_REG_ECX, &count) != UC_ERR_OK || + uc_reg_read(uc, UC_X86_REG_EDI, &destination) != UC_ERR_OK || + uc_mem_read(uc, ADDRESS + 0x300, result, sizeof(result)) != UC_ERR_OK) { uc_close(uc); - return; + return 1; } - uc_reg_read(uc, UC_X86_REG_EAX, &r_eax); - uc_reg_read(uc, UC_X86_REG_ECX, &r_ecx); - uc_reg_read(uc, UC_X86_REG_EDI, &r_edi); - uc_reg_read(uc, UC_X86_REG_EFLAGS, &eflags); - - uc_close(uc); - - printf("\n>>> Emulation done. Below is the CPU context\n"); - printf(">>> EAX = 0x%08X\n", r_eax); - printf(">>> ECX = 0x%08X\n", r_ecx); - printf(">>> EDI = 0x%08X\n", r_edi); - printf(">>> EFLAGS = 0x%08X\n", eflags); - - printf("\nHook called %lu times. Test %s\n", hook_called, (hook_called == ECX_OPS ? "PASSED!!" : "FAILED!!!")); - -} + err = uc_close(uc); + if (err != UC_ERR_OK) { + return 1; + } -int main(int argc, char *argv[]) -{ - VM_exec(); - return 0; + return hook_count == ITERATIONS + 1 && count == 0 && + destination == ADDRESS + 0x300 + sizeof(result) && + result[0] == value && result[1] == value + ? 0 + : 1; } diff --git a/tests/regress/invalid_read_in_cpu_tb_exec.c b/tests/regress/invalid_read_in_cpu_tb_exec.c index 01e8a980b8..2060354ac3 100644 --- a/tests/regress/invalid_read_in_cpu_tb_exec.c +++ b/tests/regress/invalid_read_in_cpu_tb_exec.c @@ -1,33 +1,31 @@ #include -static void hook_block(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { - printf("hook_block(%p, %"PRIx64", %d, %p)\n", uc, address, size, user_data); -} +#define ADDRESS 0x1000000 + +int main(void) +{ + const uint8_t code[] = { + 0x80, 0x05, 0xff, 0xff, 0xff, 0xff, 0x30, 0xeb, 0xf7, 0x30, + }; + uint8_t immediate = UINT8_MAX; + uint64_t rip = UINT64_MAX; + uc_engine *uc; -/* - * Disassembly according to capstone: - * add byte ptr [rip - 1], 0x30 - * jmp 0x1000000 - */ -#define BINARY "\x80\x05\xff\xff\xff\xff\x30\xeb\xf7\x30" -#define MEMORY_SIZE 2 * 1024 * 1024 -#define STARTING_ADDRESS 0x1000000 + if (uc_open(UC_ARCH_X86, UC_MODE_64, &uc) != UC_ERR_OK) { + return 1; + } + if (uc_mem_map(uc, ADDRESS, 0x200000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 20) != UC_ERR_OK || + uc_mem_read(uc, ADDRESS + 6, &immediate, sizeof(immediate)) != + UC_ERR_OK || + uc_reg_read(uc, UC_X86_REG_RIP, &rip) != UC_ERR_OK) { + uc_close(uc); + return 1; + } -int main(int argc, char **argv, char **envp) { - uc_engine *uc; - if (uc_open(UC_ARCH_X86, UC_MODE_64, &uc)) { - printf("uc_open(…) failed\n"); - return 1; - } - uc_mem_map(uc, STARTING_ADDRESS, MEMORY_SIZE, UC_PROT_ALL); - if (uc_mem_write(uc, STARTING_ADDRESS, BINARY, sizeof(BINARY) - 1)) { - printf("uc_mem_write(…) failed\n"); - return 1; - } - uc_hook hook; - uc_hook_add(uc, &hook, UC_HOOK_BLOCK, hook_block, NULL, 1, 0); - printf("uc_emu_start(…)\n"); - uc_emu_start(uc, STARTING_ADDRESS, STARTING_ADDRESS + sizeof(BINARY) - 1, 0, 20); - printf("done\n"); - return 0; + if (uc_close(uc) != UC_ERR_OK) { + return 1; + } + return immediate == 0 && rip == ADDRESS ? 0 : 1; } diff --git a/tests/regress/invalid_read_in_tb_flush_x86_64.c b/tests/regress/invalid_read_in_tb_flush_x86_64.c index dc2ca49404..bd690f1675 100644 --- a/tests/regress/invalid_read_in_tb_flush_x86_64.c +++ b/tests/regress/invalid_read_in_tb_flush_x86_64.c @@ -1,27 +1,32 @@ #include -#define HARDWARE_ARCHITECTURE UC_ARCH_X86 -#define HARDWARE_MODE UC_MODE_64 +#define ADDRESS 0x1000000 -#define MEMORY_STARTING_ADDRESS 0x1000000 -#define MEMORY_SIZE 2 * 1024 * 1024 -#define MEMORY_PERMISSIONS UC_PROT_READ +int main(void) +{ + const uint8_t code[] = {0x90}; + uint64_t rip = 0; + uc_engine *uc; + uc_err err; -#define BINARY_CODE "\x90" + err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc); + if (err != UC_ERR_OK) { + return 1; + } + if (uc_mem_map(uc, ADDRESS, 0x200000, UC_PROT_READ) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK) { + uc_close(uc); + return 1; + } -int main(int argc, char **argv, char **envp) { - uc_engine *uc; - if (uc_open(HARDWARE_ARCHITECTURE, HARDWARE_MODE, &uc)) { - printf("uc_open(…) failed\n"); - return 1; - } - uc_mem_map(uc, MEMORY_STARTING_ADDRESS, MEMORY_SIZE, MEMORY_PERMISSIONS); - if (uc_mem_write(uc, MEMORY_STARTING_ADDRESS, BINARY_CODE, sizeof(BINARY_CODE) - 1)) { - printf("uc_mem_write(…) failed\n"); - return 1; - } - printf("uc_emu_start(…)\n"); - uc_emu_start(uc, MEMORY_STARTING_ADDRESS, MEMORY_STARTING_ADDRESS + sizeof(BINARY_CODE) - 1, 0, 20); - printf("done\n"); - return 0; + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 1); + if (err != UC_ERR_FETCH_PROT || + uc_reg_read(uc, UC_X86_REG_RIP, &rip) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + if (uc_close(uc) != UC_ERR_OK) { + return 1; + } + return rip == ADDRESS ? 0 : 1; } diff --git a/tests/regress/invalid_write_in_cpu_tb_exec_x86_64.c b/tests/regress/invalid_write_in_cpu_tb_exec_x86_64.c index 47c19f090e..8a37a2dc43 100644 --- a/tests/regress/invalid_write_in_cpu_tb_exec_x86_64.c +++ b/tests/regress/invalid_write_in_cpu_tb_exec_x86_64.c @@ -1,26 +1,25 @@ #include -/* - * Disassembly according to capstone: - * mulx rsp, rsp, rdx - */ -#define BINARY "\xc4\xe2\xdb\xf6\xe2" -#define MEMORY_SIZE 2 * 1024 * 1024 -#define STARTING_ADDRESS 0x1000000 +#define ADDRESS 0x1000000 -int main(int argc, char **argv, char **envp) { - uc_engine *uc; - if (uc_open(UC_ARCH_X86, UC_MODE_64, &uc)) { - printf("uc_open(…) failed\n"); - return 1; - } - uc_mem_map(uc, STARTING_ADDRESS, MEMORY_SIZE, UC_PROT_ALL); - if (uc_mem_write(uc, STARTING_ADDRESS, BINARY, sizeof(BINARY) - 1)) { - printf("uc_mem_write(…) failed\n"); - return 1; - } - printf("uc_emu_start(…)\n"); - uc_emu_start(uc, STARTING_ADDRESS, STARTING_ADDRESS + sizeof(BINARY) - 1, 0, 20); - printf("done\n"); - return 0; +int main(void) +{ + /* mulx rsp, rsp, rdx */ + const uint8_t code[] = {0xc4, 0xe2, 0xdb, 0xf6, 0xe2}; + uint64_t rdx = 3; + uc_engine *uc; + + if (uc_open(UC_ARCH_X86, UC_MODE_64, &uc) != UC_ERR_OK) { + return 1; + } + if (uc_ctl_set_cpu_model(uc, UC_CPU_X86_HASWELL) != UC_ERR_OK || + uc_mem_map(uc, ADDRESS, 0x200000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_RDX, &rdx) != UC_ERR_OK || + uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 1) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + + return uc_close(uc) == UC_ERR_OK ? 0 : 1; } diff --git a/tests/regress/map_crash.c b/tests/regress/map_crash.c index 69a2280c70..b64d4e0ae6 100644 --- a/tests/regress/map_crash.c +++ b/tests/regress/map_crash.c @@ -1,32 +1,35 @@ -#include -#include -#include #include +#include + +#include -#define UC_BUG_WRITE_SIZE 13000 -#define UC_BUG_WRITE_ADDR 0x1000 +#define ADDRESS 0x1000 +#define INVALID_SIZE 13000 +#define VALID_SIZE 0x4000 int main(void) { - int size; - uint8_t *buf; + uint8_t *data; uc_engine *uc; - uc_err err = uc_open (UC_ARCH_X86, UC_MODE_64, &uc); - if (err) { - fprintf (stderr, "Cannot initialize unicorn\n"); + + data = malloc(VALID_SIZE); + if (data == NULL) { return 1; } - size = UC_BUG_WRITE_SIZE; - buf = malloc (size); - if (!buf) { - fprintf (stderr, "Cannot allocate\n"); + memset(data, 0xa5, VALID_SIZE); + + if (uc_open(UC_ARCH_X86, UC_MODE_64, &uc) != UC_ERR_OK) { + free(data); return 1; } - memset (buf, 0, size); - if (!uc_mem_map (uc, UC_BUG_WRITE_ADDR, size, UC_PROT_ALL)) { - uc_mem_write (uc, UC_BUG_WRITE_ADDR, buf, size); + if (uc_mem_map(uc, ADDRESS, INVALID_SIZE, UC_PROT_ALL) != UC_ERR_ARG || + uc_mem_map(uc, ADDRESS, VALID_SIZE, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, data, VALID_SIZE) != UC_ERR_OK) { + uc_close(uc); + free(data); + return 1; } - uc_close(uc); - free(buf); - return 0; + + free(data); + return uc_close(uc) == UC_ERR_OK ? 0 : 1; } diff --git a/tests/regress/map_write.c b/tests/regress/map_write.c index ef71b2ea0f..7435bc6bf9 100644 --- a/tests/regress/map_write.c +++ b/tests/regress/map_write.c @@ -1,54 +1,74 @@ -#include #include #include -#define ADDR 0x00400000 -#define SIZE 1024*64 -#define OVERFLOW 1 +#include + +#define MAP_ADDRESS UINT64_C(0x400000) +#define MAP_SIZE (64 * 1024) +#define OVERFLOW_SIZE (MAP_SIZE + 1) int main(void) { + uint8_t *input = NULL; + uint8_t *output = NULL; uc_engine *uc = NULL; - uint8_t *buf = NULL, *buf2 = NULL; - int i; uc_err err; + size_t i; + + input = malloc(OVERFLOW_SIZE); + output = malloc(MAP_SIZE); + if (input == NULL || output == NULL) { + fprintf(stderr, "allocation failed\n"); + goto fail; + } + for (i = 0; i < OVERFLOW_SIZE; i++) { + input[i] = (uint8_t)i; + } + + err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_open failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_map(uc, MAP_ADDRESS, MAP_SIZE, UC_PROT_ALL); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_map failed: %s\n", uc_strerror(err)); + goto fail; + } + + err = uc_mem_write(uc, MAP_ADDRESS, input, OVERFLOW_SIZE); + if (err != UC_ERR_WRITE_UNMAPPED) { + fprintf(stderr, "expected UC_ERR_WRITE_UNMAPPED, got %s\n", + uc_strerror(err)); + goto fail; + } + err = uc_mem_read(uc, MAP_ADDRESS, output, MAP_SIZE); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_read failed: %s\n", uc_strerror(err)); + goto fail; + } + for (i = 0; i < MAP_SIZE; i++) { + if (output[i] != 0) { + fprintf(stderr, "failed write changed byte %zu\n", i); + goto fail; + } + } + + err = uc_close(uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_close failed: %s\n", uc_strerror(err)); + uc = NULL; + goto fail; + } + free(output); + free(input); + return 0; - err = uc_open (UC_ARCH_X86, UC_MODE_64, &uc); - if (err) { - printf ("uc_open %d\n", err); - goto exit; - } - err = uc_mem_map (uc, ADDR, SIZE, UC_PROT_ALL); - if (err) { - printf ("uc_mem_map %d\n", err); - goto exit; - } - buf = calloc (SIZE*2, 1); - buf2 = calloc (SIZE, 1); - for (i=0;i +#include +#include +#include +#include + +#include + +#define CODE_ADDRESS 0x100000 +#define WRITE_ADDRESS 0x100100 +#define DATA_ADDRESS 0x101000 + +typedef struct StopHookContext { + uc_err stop_error; + uc_mem_type type; + uint64_t address; + int64_t value; + uint32_t size; + unsigned int count; +} StopHookContext; + +static void stop_memory_access(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, + void *user_data) +{ + StopHookContext *context = user_data; + + context->type = type; + context->address = address; + context->size = (uint32_t)size; + context->value = value; + context->count++; + context->stop_error = uc_emu_stop(uc); +} + +static void report_uc_error(const char *operation, uc_err error) +{ + fprintf(stderr, "%s failed with %u: %s\n", operation, (unsigned)error, + uc_strerror(error)); +} + +static bool check_hook(const StopHookContext *context, uc_mem_type type, + int64_t value) +{ + if (context->stop_error != UC_ERR_OK || context->count != 1 || + context->type != type || context->address != DATA_ADDRESS || + context->size != 4 || + (type == UC_MEM_WRITE && context->value != value)) { + fprintf(stderr, + "unexpected hook state: error=%u count=%u type=%u " + "address=0x%" PRIx64 " size=%" PRIu32 " value=0x%" PRIx64 "\n", + (unsigned)context->stop_error, context->count, + (unsigned)context->type, context->address, context->size, + (uint64_t)context->value); + return false; + } + return true; +} + +int main(void) +{ + static const uint8_t read_code[] = { + 0xa1, 0x00, 0x10, 0x10, 0x00, /* mov eax, [0x101000] */ + 0x43, /* inc ebx */ + }; + static const uint8_t write_code[] = { + 0xa3, 0x00, 0x10, 0x10, 0x00, /* mov [0x101000], eax */ + 0x43, /* inc ebx */ + }; + StopHookContext context = {0}; + const uint32_t original_data = 0x11223344; + const uint32_t original_eax = 0xdeadbeef; + const uint32_t write_eax = 0x87654321; + uc_engine *uc = NULL; + uc_hook hook; + uc_err error; + uint32_t eax; + uint32_t ebx = 0; + uint32_t data; + bool failed = false; + + error = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); + if (error != UC_ERR_OK) { + report_uc_error("uc_open", error); + return EXIT_FAILURE; + } + error = uc_mem_map(uc, CODE_ADDRESS, 0x2000, UC_PROT_ALL); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_map", error); + failed = true; + goto cleanup; + } + error = uc_mem_write(uc, CODE_ADDRESS, read_code, sizeof(read_code)); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_write(read code)", error); + failed = true; + goto cleanup; + } + error = uc_mem_write(uc, WRITE_ADDRESS, write_code, sizeof(write_code)); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_write(write code)", error); + failed = true; + goto cleanup; + } + error = + uc_mem_write(uc, DATA_ADDRESS, &original_data, sizeof(original_data)); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_write(data)", error); + failed = true; + goto cleanup; + } + + error = uc_reg_write(uc, UC_X86_REG_EAX, &original_eax); + if (error != UC_ERR_OK) { + report_uc_error("uc_reg_write(EAX)", error); + failed = true; + goto cleanup; + } + error = uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, stop_memory_access, + &context, DATA_ADDRESS, DATA_ADDRESS); + if (error != UC_ERR_OK) { + report_uc_error("uc_hook_add(read)", error); + failed = true; + goto cleanup; + } + error = + uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + sizeof(read_code), 0, 0); + if (error != UC_ERR_OK) { + report_uc_error("read uc_emu_start", error); + failed = true; + } + eax = 0; + error = uc_reg_read(uc, UC_X86_REG_EAX, &eax); + if (error != UC_ERR_OK || eax != original_eax || + !check_hook(&context, UC_MEM_READ, 0)) { + fprintf(stderr, "read continued after stop: eax=0x%" PRIx32 "\n", eax); + failed = true; + } + error = uc_hook_del(uc, hook); + if (error != UC_ERR_OK) { + report_uc_error("uc_hook_del(read)", error); + failed = true; + } + + context = (StopHookContext){0}; + error = uc_reg_write(uc, UC_X86_REG_EAX, &write_eax); + error = error == UC_ERR_OK ? uc_reg_write(uc, UC_X86_REG_EBX, &ebx) : error; + if (error != UC_ERR_OK) { + report_uc_error("uc_reg_write(write registers)", error); + failed = true; + goto cleanup; + } + error = uc_hook_add(uc, &hook, UC_HOOK_MEM_WRITE, stop_memory_access, + &context, DATA_ADDRESS, DATA_ADDRESS); + if (error != UC_ERR_OK) { + report_uc_error("uc_hook_add(write)", error); + failed = true; + goto cleanup; + } + error = uc_emu_start(uc, WRITE_ADDRESS, WRITE_ADDRESS + sizeof(write_code), + 0, 0); + if (error != UC_ERR_OK) { + report_uc_error("write uc_emu_start", error); + failed = true; + } + data = 0; + ebx = 0; + error = uc_mem_read(uc, DATA_ADDRESS, &data, sizeof(data)); + error = error == UC_ERR_OK ? uc_reg_read(uc, UC_X86_REG_EBX, &ebx) : error; + if (error != UC_ERR_OK || data != original_data || ebx != 0 || + !check_hook(&context, UC_MEM_WRITE, write_eax)) { + fprintf(stderr, + "write continued after stop: data=0x%" PRIx32 " ebx=0x%" PRIx32 + "\n", + data, ebx); + failed = true; + } + error = uc_hook_del(uc, hook); + if (error != UC_ERR_OK) { + report_uc_error("uc_hook_del(write)", error); + failed = true; + } + + error = uc_emu_start(uc, WRITE_ADDRESS, WRITE_ADDRESS + sizeof(write_code), + 0, 0); + if (error != UC_ERR_OK) { + report_uc_error("recovery uc_emu_start", error); + failed = true; + } + data = 0; + ebx = 0; + error = uc_mem_read(uc, DATA_ADDRESS, &data, sizeof(data)); + error = error == UC_ERR_OK ? uc_reg_read(uc, UC_X86_REG_EBX, &ebx) : error; + if (error != UC_ERR_OK || data != write_eax || ebx != 1) { + fprintf(stderr, + "recovery write failed: data=0x%" PRIx32 " ebx=0x%" PRIx32 "\n", + data, ebx); + failed = true; + } + +cleanup: + if (uc != NULL) { + error = uc_close(uc); + if (error != UC_ERR_OK) { + report_uc_error("uc_close", error); + failed = true; + } + } + return failed ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/tests/regress/mips_branch_likely_issue.c b/tests/regress/mips_branch_likely_issue.c index dbbf10d92f..71c56c45a7 100644 --- a/tests/regress/mips_branch_likely_issue.c +++ b/tests/regress/mips_branch_likely_issue.c @@ -1,180 +1,78 @@ -/* - Test for MIPS branch likely instructions only executing their delay slot instruction when the branch is taken. - Currently it seems to always execute the delay slot instruction like a normal non-"likely" style branch. - */ - -// windows specific -#ifdef _MSC_VER -#include -#include -#include -#define PRIx64 "llX" -#include -#ifdef _WIN64 -#pragma comment(lib, "unicorn_staload64.lib") -#else // _WIN64 -#pragma comment(lib, "unicorn_staload.lib") -#endif // _WIN64 - -// posix specific -#else // _MSC_VER #include -#include "pthread.h" -#endif // _MSC_VER -// common includes -#include +#define ADDRESS 0x100000 +#define DELAY_SLOT_ADDRESS (ADDRESS + 16) +typedef struct HookState { + unsigned int test_number; + unsigned int delay_slot_count[2]; +} HookState; -const uint64_t addr = 0x100000; -// This code SHOULD execute the instruction at 0x100010. -const unsigned char test_code_1[] = { - 0x00,0x00,0x04,0x24, // 100000: li $a0, 0 - 0x01,0x00,0x02,0x24, // 100004: li $v0, 1 - 0x02,0x00,0x03,0x24, // 100008: li $v1, 2 - 0x01,0x00,0x62,0x54, // 10000C: bnel $v1, $v0, 0x100014 - 0x21,0x20,0x62,0x00, // 100010: addu $a0, $v1, $v0 -}; -// This code SHOULD NOT execute the instruction at 0x100010. -const unsigned char test_code_2[] = { - 0x00,0x00,0x04,0x24, // 100000: li $a0, 0 - 0x01,0x00,0x02,0x24, // 100004: li $v0, 1 - 0x01,0x00,0x03,0x24, // 100008: li $v1, 1 - 0x01,0x00,0x62,0x54, // 10000C: bnel $v1, $v0, 0x100014 - 0x21,0x20,0x62,0x00, // 100010: addu $a0, $v1, $v0 -}; -int test_num = 0; -// flag for whether the delay slot was executed by the emulator -bool test1_delayslot_executed = false; -bool test2_delayslot_executed = false; -// flag for whether the delay slot had a code hook called for it -bool test1_delayslot_hooked = false; -bool test2_delayslot_hooked = false; +static void count_delay_slot(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + HookState *state = user_data; + (void)uc; + (void)size; + if (address == DELAY_SLOT_ADDRESS && state->test_number < 2) { + state->delay_slot_count[state->test_number]++; + } +} -// This hook is used to show that code is executing in the emulator. -static void mips_codehook(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) +static bool run_case(uc_engine *uc, HookState *state, const uint8_t *code, + size_t code_size, uint32_t expected_a0) { - printf("Test %d Executing: %"PRIx64"\n", test_num, address); - if( test_num == 1 && address == 0x100010 ) - { - printf("Delay slot hook called!\n"); - test1_delayslot_hooked = true; - } - if( test_num == 2 && address == 0x100010 ) - { - printf("Delay slot hook called!\n"); - test2_delayslot_hooked = true; + uint32_t a0 = UINT32_MAX; + + if (uc_mem_write(uc, ADDRESS, code, code_size) != UC_ERR_OK || + uc_emu_start(uc, ADDRESS, ADDRESS + code_size, 0, 0) != UC_ERR_OK || + uc_reg_read(uc, UC_MIPS_REG_A0, &a0) != UC_ERR_OK) { + return false; } + return a0 == expected_a0; } - -int main(int argc, char **argv, char **envp) +int main(void) { + const uint8_t taken[] = { + 0x00, 0x00, 0x04, 0x24, /* li $a0, 0 */ + 0x01, 0x00, 0x02, 0x24, /* li $v0, 1 */ + 0x02, 0x00, 0x03, 0x24, /* li $v1, 2 */ + 0x01, 0x00, 0x62, 0x54, /* bnel $v1, $v0, +1 */ + 0x21, 0x20, 0x62, 0x00, /* addu $a0, $v1, $v0 */ + }; + const uint8_t not_taken[] = { + 0x00, 0x00, 0x04, 0x24, /* li $a0, 0 */ + 0x01, 0x00, 0x02, 0x24, /* li $v0, 1 */ + 0x01, 0x00, 0x03, 0x24, /* li $v1, 1 */ + 0x01, 0x00, 0x62, 0x54, /* bnel $v1, $v0, +1 */ + 0x21, 0x20, 0x62, 0x00, /* addu $a0, $v1, $v0 */ + }; + HookState state = {0}; uc_engine *uc; - uc_err err; - uc_hook hhc; - uint32_t val; + uc_hook hook; - // Initialize emulator in MIPS 32bit little endian mode - printf("uc_open()\n"); - err = uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32, &uc); - if (err) - { - printf("Failed on uc_open() with error returned: %u\n", err); - return err; + if (uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32, &uc) != UC_ERR_OK) { + return 1; } - - // map in a page of mem - printf("uc_mem_map()\n"); - err = uc_mem_map(uc, addr, 0x1000, UC_PROT_ALL); - if (err) - { - printf("Failed on uc_mem_map() with error returned: %u\n", err); - return err; - } - - // hook all instructions by having @begin > @end - printf("uc_hook_add()\n"); - uc_hook_add(uc, &hhc, UC_HOOK_CODE, mips_codehook, NULL, 1, 0); - if( err ) - { - printf("Failed on uc_hook_add(code) with error returned: %u\n", err); - return err; + if (uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_CODE, count_delay_slot, &state, 1, 0) != + UC_ERR_OK || + !run_case(uc, &state, taken, sizeof(taken), 3)) { + uc_close(uc); + return 1; } - - // write test1 code to be emulated to memory - test_num = 1; - printf("\nuc_mem_write(1)\n"); - err = uc_mem_write(uc, addr, test_code_1, sizeof(test_code_1)); - if( err ) - { - printf("Failed on uc_mem_write() with error returned: %u\n", err); - return err; + state.test_number = 1; + if (!run_case(uc, &state, not_taken, sizeof(not_taken), 0)) { + uc_close(uc); + return 1; } - // start executing test code 1 - printf("uc_emu_start(1)\n"); - uc_emu_start(uc, addr, addr+sizeof(test_code_1), 0, 0); - // read the value from a0 when finished executing - uc_reg_read(uc, UC_MIPS_REG_A0, &val); printf("a0 is %X\n", val); - if( val != 0 ) - test1_delayslot_executed = true; - - // write test2 code to be emulated to memory - test_num = 2; - printf("\nuc_mem_write(2)\n"); - err = uc_mem_write(uc, addr, test_code_2, sizeof(test_code_2)); - if( err ) - { - printf("Failed on uc_mem_write() with error returned: %u\n", err); - return err; + if (uc_close(uc) != UC_ERR_OK) { + return 1; } - // start executing test code 2 - printf("uc_emu_start(2)\n"); - uc_emu_start(uc, addr, addr+sizeof(test_code_2), 0, 0); - // read the value from a0 when finished executing - uc_reg_read(uc, UC_MIPS_REG_A0, &val); printf("a0 is %X\n", val); - if( val != 0 ) - test2_delayslot_executed = true; - - - // free resources - printf("\nuc_close()\n"); - uc_close(uc); - - - // print test results - printf("\n\nTest 1 SHOULD execute the delay slot instruction:\n"); - printf(" Emulator %s execute the delay slot: %s\n", - test1_delayslot_executed ? "did" : "did not", - test1_delayslot_executed ? "CORRECT" : "WRONG"); - printf(" Emulator %s hook the delay slot: %s\n", - test1_delayslot_hooked ? "did" : "did not", - test1_delayslot_hooked ? "CORRECT" : "WRONG"); - - printf("\n\nTest 2 SHOULD NOT execute the delay slot instruction:\n"); - printf(" Emulator %s execute the delay slot: %s\n", - test2_delayslot_executed ? "did" : "did not", - !test2_delayslot_executed ? "CORRECT" : "WRONG"); - printf(" Emulator %s hook the delay slot: %s\n", - test2_delayslot_hooked ? "did" : "did not", - !test2_delayslot_hooked ? "CORRECT" : "WRONG"); - - - // test 1 SHOULD execute the instruction in the delay slot - if( test1_delayslot_hooked == true && test1_delayslot_executed == true ) - printf("\n\nTEST 1 PASSED!\n"); - else - printf("\n\nTEST 1 FAILED!\n"); - - // test 2 SHOULD NOT execute the instruction in the delay slot - if( test2_delayslot_hooked == false && test2_delayslot_executed == false ) - printf("TEST 2 PASSED!\n\n"); - else - printf("TEST 2 FAILED!\n\n"); - - return 0; + return state.delay_slot_count[0] == 1 && state.delay_slot_count[1] == 0 ? 0 + : 1; } - diff --git a/tests/regress/mips_delay_slot_code_hook.c b/tests/regress/mips_delay_slot_code_hook.c index e5452e6dcb..47be881ead 100644 --- a/tests/regress/mips_delay_slot_code_hook.c +++ b/tests/regress/mips_delay_slot_code_hook.c @@ -1,124 +1,59 @@ -/* -Test for code hook being called for instructions in branch delay slot in MIPS cpu. -See issue https://github.com/unicorn-engine/unicorn/issues/290 - -The code hook should be called for every instruction executed. -This test checks that the code hook is correctly called for instructions in branch delay slots. -In this test the loop check value is decremented inside the branch delay shot. -This helps to show that the instruction in the branch delay slot is being executed, -but that the code hook is just not occurring. -*/ - -// windows specific -#ifdef _MSC_VER -#include -#include -#define PRIx64 "llX" -#include -#ifdef _WIN64 -#pragma comment(lib, "unicorn_staload64.lib") -#else // _WIN64 -#pragma comment(lib, "unicorn_staload.lib") -#endif // _WIN64 - -// posix specific -#else // _MSC_VER #include -#endif // _MSC_VER -// common includes -#include +#define ADDRESS 0x100000 +#define DELAY_SLOT_ADDRESS (ADDRESS + 12) +static unsigned int delay_slot_hook_count; +static unsigned int loop_count; -// Test MIPS little endian code. -// It should loop 3 times before ending. -const uint64_t addr = 0x100000; -const unsigned char loop_test_code[] = { - 0x02,0x00,0x04,0x24, // 100000: li $a0, 2 - // loop1 - 0x00,0x00,0x00,0x00, // 100004: nop - 0xFE,0xFF,0x80,0x14, // 100008: bnez $a0, loop1 - 0xFF,0xFF,0x84,0x24, // 10000C: addiu $a0, -1 -}; -bool test_passed_ok = false; -int loop_count = 0; - - -static void mips_codehook(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) +static void count_instructions(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) { - if( address == 0x10000C ) - test_passed_ok = true; - if( address == 0x100004 ) - { - printf("\nloop %d:\n", loop_count); + (void)uc; + (void)size; + (void)user_data; + if (address == ADDRESS + 4) { loop_count++; + } else if (address == DELAY_SLOT_ADDRESS) { + delay_slot_hook_count++; } - printf("Code: %"PRIx64"\n", address); } - -int main(int argc, char **argv, char **envp) +int main(void) { + const uint8_t code[] = { + 0x02, 0x00, 0x04, 0x24, /* li $a0, 2 */ + 0x00, 0x00, 0x00, 0x00, /* nop */ + 0xfe, 0xff, 0x80, 0x14, /* bnez $a0, -2 */ + 0xff, 0xff, 0x84, 0x24, /* addiu $a0, -1 */ + }; + uint32_t a0 = 0; uc_engine *uc; + uc_hook hook; uc_err err; - uc_hook hhc; - uint32_t val; - // Initialize emulator in MIPS 32bit little endian mode err = uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32, &uc); - if (err) - { - printf("Failed on uc_open() with error returned: %u\n", err); - return err; + if (err != UC_ERR_OK) { + return 1; } - - // map in a page of mem - err = uc_mem_map(uc, addr, 0x1000, UC_PROT_ALL); - if (err) - { - printf("Failed on uc_mem_map() with error returned: %u\n", err); - return err; + if (uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_CODE, count_instructions, NULL, 1, 0) != + UC_ERR_OK) { + uc_close(uc); + return 1; } - // write machine code to be emulated to memory - err = uc_mem_write(uc, addr, loop_test_code, sizeof(loop_test_code)); - if( err ) - { - printf("Failed on uc_mem_write() with error returned: %u\n", err); - return err; - } - - // hook all instructions by having @begin > @end - uc_hook_add(uc, &hhc, UC_HOOK_CODE, mips_codehook, NULL, 1, 0); - if( err ) - { - printf("Failed on uc_hook_add(code) with error returned: %u\n", err); - return err; - } - - // execute code - printf("---- Executing Code ----\n"); - err = uc_emu_start(uc, addr, addr + sizeof(loop_test_code), 0, 0); - if (err) - { - printf("Failed on uc_emu_start() with error returned %u: %s\n", - err, uc_strerror(err)); - return err; + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_OK || uc_reg_read(uc, UC_MIPS_REG_A0, &a0) != UC_ERR_OK) { + uc_close(uc); + return 1; } - // done executing, print some reg values as a test - printf("---- Execution Complete ----\n\n"); - uc_reg_read(uc, UC_MIPS_REG_PC, &val); printf("pc is %X\n", val); - uc_reg_read(uc, UC_MIPS_REG_A0, &val); printf("a0 is %X\n", val); - - // free resources - uc_close(uc); - - if( test_passed_ok ) - printf("\n\nTEST PASSED!\n\n"); - else - printf("\n\nTEST FAILED!\n\n"); - - return 0; + if (uc_close(uc) != UC_ERR_OK) { + return 1; + } + return a0 == UINT32_MAX && loop_count == 3 && delay_slot_hook_count == 3 + ? 0 + : 1; } - diff --git a/tests/regress/mips_invalid_read_of_size_4_when_tracing.c b/tests/regress/mips_invalid_read_of_size_4_when_tracing.c index 013016e41c..4e12e24ddd 100644 --- a/tests/regress/mips_invalid_read_of_size_4_when_tracing.c +++ b/tests/regress/mips_invalid_read_of_size_4_when_tracing.c @@ -1,33 +1,42 @@ #include -static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { - printf("tracing\n"); -} +#define ADDRESS 0x1000000 + +static unsigned int hook_count; -#define HARDWARE_ARCHITECTURE UC_ARCH_MIPS -#define HARDWARE_MODE UC_MODE_MIPS32 +static void count_instruction(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + (void)uc; + (void)address; + (void)size; + (void)user_data; + hook_count++; +} -#define MEMORY_STARTING_ADDRESS 0x1000000 -#define MEMORY_SIZE 2 * 1024 * 1024 -#define MEMORY_PERMISSIONS UC_PROT_ALL +int main(void) +{ + const uint8_t code[] = "00000000000000000000000000AA"; + uint32_t pc = 0; + uc_engine *uc; + uc_hook hook; -#define BINARY_CODE "00000000000000000000000000AA" + if (uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32, &uc) != UC_ERR_OK) { + return 1; + } + if (uc_mem_map(uc, ADDRESS, 0x200000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code) - 1) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_CODE, count_instruction, NULL, ADDRESS, + ADDRESS + 1) != UC_ERR_OK || + uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code) - 1, 0, 100) != + UC_ERR_OK || + uc_reg_read(uc, UC_MIPS_REG_PC, &pc) != UC_ERR_OK) { + uc_close(uc); + return 1; + } -int main(int argc, char **argv, char **envp) { - uc_engine *uc; - if (uc_open(HARDWARE_ARCHITECTURE, HARDWARE_MODE, &uc)) { - printf("uc_open(…) failed\n"); - return 1; - } - uc_mem_map(uc, MEMORY_STARTING_ADDRESS, MEMORY_SIZE, MEMORY_PERMISSIONS); - if (uc_mem_write(uc, MEMORY_STARTING_ADDRESS, BINARY_CODE, sizeof(BINARY_CODE) - 1)) { - printf("uc_mem_write(…) failed\n"); - return 1; - } - uc_hook trace; - uc_hook_add(uc, &trace, UC_HOOK_CODE, hook_code, NULL, MEMORY_STARTING_ADDRESS, MEMORY_STARTING_ADDRESS + 1); - printf("uc_emu_start(…)\n"); - uc_emu_start(uc, MEMORY_STARTING_ADDRESS, MEMORY_STARTING_ADDRESS + sizeof(BINARY_CODE) - 1, 0, 0); - printf("done\n"); - return 0; + if (uc_close(uc) != UC_ERR_OK) { + return 1; + } + return pc == ADDRESS + 24 && hook_count == 1 ? 0 : 1; } diff --git a/tests/regress/mips_kseg0_1.c b/tests/regress/mips_kseg0_1.c index 7b650ef6f8..a7485ba4a1 100644 --- a/tests/regress/mips_kseg0_1.c +++ b/tests/regress/mips_kseg0_1.c @@ -1,75 +1,36 @@ #include -#include -#include -#include -// Test for the MIPS kseg0 and kseg1 memory segments. -// See issue https://github.com/unicorn-engine/unicorn/issues/217 -// The kseg0 address range 0x80000000-0x9FFFFFFF is not mapped through the MMU, -// but instead is directly translated to low ram by masking off the high address bit. -// Similarly, the address range kseg1 0xA00000000-0xBFFFFFF is translated directly to -// low ram by masking off the top 3 address bits. -// Qemu handles these address ranges correctly, but there are issues with the way Unicorn checks for -// a valid memory mapping when executing code in the kseg0 or kseg1 memory range. -// In particular, Unicorn checks for a valid mapping using the virtual address when executing from kseg0/1, -// when it should probably use the real address in low ram. +#define PHYSICAL_ADDRESS 0x1000 +#define KSEG0_ADDRESS 0x80001000 +#define KSEG1_ADDRESS 0xa0001000 -#define KSEG0_VIRT_ADDRESS 0x80001000 //Virtual address in kseg0, mapped by processor (and QEMU) to 0x1000 -#define KSEG1_VIRT_ADDRESS 0xA0001000 //Virtual address in kseg1, mapped by processor (and QEMU) to 0x1000 -#define KSEG0_1_REAL_ADDRESS 0x1000 //Real address corresponding to the above addresses in kseg0/1 +static bool run_at(uc_engine *uc, uint64_t address) +{ + uint32_t at = 0; -#define MIPS_CODE_EL "\x56\x34\x21\x34" // ori $at, $at, 0x3456; + if (uc_reg_write(uc, UC_MIPS_REG_AT, &at) != UC_ERR_OK || + uc_emu_start(uc, address, address + 4, 0, 1) != UC_ERR_OK || + uc_reg_read(uc, UC_MIPS_REG_AT, &at) != UC_ERR_OK) { + return false; + } + return at == 0x3456; +} int main(void) { - + const uint8_t code[] = {0x56, 0x34, 0x21, 0x34}; /* ori $at, 0x3456 */ uc_engine *uc; - uc_err err; - - err = uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32, &uc); - if (err) { - printf("uc_open %d\n", err); - return 1; - } - // map 4Kb memory for this emulation, into the real address space - err = uc_mem_map(uc, KSEG0_1_REAL_ADDRESS, 4 * 1024, UC_PROT_ALL); - if (err) { - printf("uc_mem_map %d\n", err); + if (uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32, &uc) != UC_ERR_OK) { return 1; } - - // write machine code to be emulated to memory - err = uc_mem_write(uc, KSEG0_1_REAL_ADDRESS, MIPS_CODE_EL, sizeof(MIPS_CODE_EL) - 1); - if (err) { - printf("uc_mem_map %s\n", uc_strerror(err)); + if (uc_mem_map(uc, PHYSICAL_ADDRESS, 0x1000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, PHYSICAL_ADDRESS, code, sizeof(code)) != UC_ERR_OK || + !run_at(uc, PHYSICAL_ADDRESS) || !run_at(uc, KSEG0_ADDRESS) || + !run_at(uc, KSEG1_ADDRESS)) { + uc_close(uc); return 1; } - //Start emulation at real address, this currently succeeds - err = uc_emu_start(uc, KSEG0_1_REAL_ADDRESS, KSEG0_1_REAL_ADDRESS + 4, 0, 0); - if (err) { - printf("uc_emu_start at real address: %s\n", uc_strerror(err)); - return 1; - } - - //Start emulation at virtual address in kseg0, this cuurently fails - err = uc_emu_start(uc, KSEG0_VIRT_ADDRESS, KSEG0_VIRT_ADDRESS + 4, 0, 0); - if (err) { - printf("uc_emu_start at kseg0 address: %s\n", uc_strerror(err)); - return 1; - } - - //Start emulation at virtual address in kseg1, this currently fails - err = uc_emu_start(uc, KSEG1_VIRT_ADDRESS, KSEG1_VIRT_ADDRESS + 4, 0, 0); - if (err) { - printf("uc_emu_start at kseg1 address: %s\n", uc_strerror(err)); - return 1; - } - - uc_close(uc); - - printf("Good, this bug is fixed!\n"); - - return 0; + return uc_close(uc) == UC_ERR_OK ? 0 : 1; } diff --git a/tests/regress/nested_stop_state.c b/tests/regress/nested_stop_state.c new file mode 100644 index 0000000000..a97b044390 --- /dev/null +++ b/tests/regress/nested_stop_state.c @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include + +#include + +#define OUTER_ADDRESS 0x100000 +#define INNER_ADDRESS 0x101000 +#define RECOVERY_ADDRESS 0x102000 + +typedef struct NestedStopContext { + uc_err stop_error; + uc_err nested_error; + unsigned int callback_count; +} NestedStopContext; + +typedef struct InnerStopContext { + uc_err nested_error; + uc_err stop_error; + unsigned int outer_callback_count; + unsigned int inner_callback_count; +} InnerStopContext; + +static void stop_and_run_nested(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + NestedStopContext *context = user_data; + + (void)address; + (void)size; + + context->callback_count++; + context->stop_error = uc_emu_stop(uc); + if (context->stop_error == UC_ERR_OK) { + context->nested_error = + uc_emu_start(uc, INNER_ADDRESS, INNER_ADDRESS + 2, 0, 0); + } +} + +static void report_uc_error(const char *operation, uc_err error) +{ + fprintf(stderr, "%s failed with %u: %s\n", operation, (unsigned)error, + uc_strerror(error)); +} + +static void stop_inner_run(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + InnerStopContext *context = user_data; + + (void)address; + (void)size; + + context->inner_callback_count++; + context->stop_error = uc_emu_stop(uc); +} + +static void run_stopped_inner(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + InnerStopContext *context = user_data; + + (void)address; + (void)size; + + context->outer_callback_count++; + context->nested_error = + uc_emu_start(uc, INNER_ADDRESS, INNER_ADDRESS + 2, 0, 0); +} + +int main(void) +{ + static const uint8_t outer_code[] = { + 0xff, 0xc0, /* inc eax */ + 0xff, 0xc3, /* inc ebx */ + 0xff, 0xc1, /* inc ecx */ + }; + static const uint8_t inner_code[] = { + 0xff, 0xc2, /* inc edx */ + }; + static const uint8_t recovery_code[] = { + 0xff, 0xc6, /* inc esi */ + }; + NestedStopContext context = {UC_ERR_OK, UC_ERR_OK, 0}; + InnerStopContext inner_stop = {UC_ERR_OK, UC_ERR_OK, 0, 0}; + uc_engine *uc = NULL; + uc_hook hook; + uc_hook inner_hook; + uc_err error; + uint32_t eax = 0; + uint32_t ebx = 0; + uint32_t ecx = 0; + uint32_t edx = 0; + uint32_t esi = 0; + bool failed = false; + + error = uc_open(UC_ARCH_X86, UC_MODE_64, &uc); + if (error != UC_ERR_OK) { + report_uc_error("uc_open", error); + return EXIT_FAILURE; + } + + error = uc_mem_map(uc, OUTER_ADDRESS, 0x3000, UC_PROT_ALL); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_map", error); + failed = true; + goto cleanup; + } + error = uc_mem_write(uc, OUTER_ADDRESS, outer_code, sizeof(outer_code)); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_write(outer)", error); + failed = true; + goto cleanup; + } + error = uc_mem_write(uc, INNER_ADDRESS, inner_code, sizeof(inner_code)); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_write(inner)", error); + failed = true; + goto cleanup; + } + error = uc_mem_write(uc, RECOVERY_ADDRESS, recovery_code, + sizeof(recovery_code)); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_write(recovery)", error); + failed = true; + goto cleanup; + } + + error = uc_hook_add(uc, &hook, UC_HOOK_CODE, stop_and_run_nested, &context, + OUTER_ADDRESS, OUTER_ADDRESS); + if (error != UC_ERR_OK) { + report_uc_error("uc_hook_add", error); + failed = true; + goto cleanup; + } + + error = uc_emu_start(uc, OUTER_ADDRESS, OUTER_ADDRESS + sizeof(outer_code), + 0, 0); + if (error != UC_ERR_OK) { + report_uc_error("outer uc_emu_start", error); + failed = true; + } + if (context.stop_error != UC_ERR_OK) { + report_uc_error("nested callback uc_emu_stop", context.stop_error); + failed = true; + } + if (context.nested_error != UC_ERR_OK) { + report_uc_error("nested uc_emu_start", context.nested_error); + failed = true; + } + if (context.callback_count != 1) { + fprintf(stderr, "unexpected callback count: %u\n", + context.callback_count); + failed = true; + } + + error = uc_reg_read(uc, UC_X86_REG_EAX, &eax); + error = error == UC_ERR_OK ? uc_reg_read(uc, UC_X86_REG_EBX, &ebx) : error; + error = error == UC_ERR_OK ? uc_reg_read(uc, UC_X86_REG_ECX, &ecx) : error; + error = error == UC_ERR_OK ? uc_reg_read(uc, UC_X86_REG_EDX, &edx) : error; + if (error != UC_ERR_OK) { + report_uc_error("uc_reg_read", error); + failed = true; + } else if (eax != 0 || ebx != 0 || ecx != 0 || edx != 1) { + fprintf(stderr, + "unexpected registers after nested stop: eax=%" PRIu32 + " ebx=%" PRIu32 " ecx=%" PRIu32 " edx=%" PRIu32 "\n", + eax, ebx, ecx, edx); + failed = true; + } + + error = uc_emu_start(uc, RECOVERY_ADDRESS, + RECOVERY_ADDRESS + sizeof(recovery_code), 0, 0); + if (error != UC_ERR_OK) { + report_uc_error("recovery uc_emu_start", error); + failed = true; + } + error = uc_reg_read(uc, UC_X86_REG_ESI, &esi); + if (error != UC_ERR_OK) { + report_uc_error("uc_reg_read(ESI)", error); + failed = true; + } else if (esi != 1) { + fprintf(stderr, "unexpected recovery ESI value: %" PRIu32 "\n", esi); + failed = true; + } + + error = uc_hook_del(uc, hook); + if (error != UC_ERR_OK) { + report_uc_error("uc_hook_del(outer stop)", error); + failed = true; + } + eax = 0; + ebx = 0; + ecx = 0; + edx = 0; + error = uc_reg_write(uc, UC_X86_REG_EAX, &eax); + error = error == UC_ERR_OK ? uc_reg_write(uc, UC_X86_REG_EBX, &ebx) : error; + error = error == UC_ERR_OK ? uc_reg_write(uc, UC_X86_REG_ECX, &ecx) : error; + error = error == UC_ERR_OK ? uc_reg_write(uc, UC_X86_REG_EDX, &edx) : error; + if (error == UC_ERR_OK) { + error = uc_hook_add(uc, &inner_hook, UC_HOOK_CODE, stop_inner_run, + &inner_stop, INNER_ADDRESS, INNER_ADDRESS); + } + if (error == UC_ERR_OK) { + error = uc_hook_add(uc, &hook, UC_HOOK_CODE, run_stopped_inner, + &inner_stop, OUTER_ADDRESS, OUTER_ADDRESS); + } + if (error != UC_ERR_OK) { + report_uc_error("inner stop setup", error); + failed = true; + goto cleanup; + } + + error = uc_emu_start(uc, OUTER_ADDRESS, OUTER_ADDRESS + sizeof(outer_code), + 0, 0); + if (error != UC_ERR_OK || inner_stop.nested_error != UC_ERR_OK || + inner_stop.stop_error != UC_ERR_OK) { + report_uc_error("inner stop emulation", error); + failed = true; + } + error = uc_reg_read(uc, UC_X86_REG_EAX, &eax); + error = error == UC_ERR_OK ? uc_reg_read(uc, UC_X86_REG_EBX, &ebx) : error; + error = error == UC_ERR_OK ? uc_reg_read(uc, UC_X86_REG_ECX, &ecx) : error; + error = error == UC_ERR_OK ? uc_reg_read(uc, UC_X86_REG_EDX, &edx) : error; + if (error != UC_ERR_OK || inner_stop.outer_callback_count != 1 || + inner_stop.inner_callback_count != 1 || eax != 1 || ebx != 1 || + ecx != 1 || edx != 0) { + fprintf(stderr, + "inner stop escaped its frame: outer=%u inner=%u eax=%" PRIu32 + " ebx=%" PRIu32 " ecx=%" PRIu32 " edx=%" PRIu32 "\n", + inner_stop.outer_callback_count, + inner_stop.inner_callback_count, eax, ebx, ecx, edx); + failed = true; + } + +cleanup: + if (uc != NULL) { + error = uc_close(uc); + if (error != UC_ERR_OK) { + report_uc_error("uc_close", error); + failed = true; + } + } + + return failed ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/tests/regress/nr_mem_test.c b/tests/regress/nr_mem_test.c index 34a8814d08..c4923684b4 100644 --- a/tests/regress/nr_mem_test.c +++ b/tests/regress/nr_mem_test.c @@ -1,108 +1,71 @@ -/* -Non-readable memory test case - -Copyright(c) 2015 Chris Eagle - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -version 2 as published by the Free Software Foundation. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -*/ - -#include - #include -const uint8_t PROGRAM[] = - "\x8b\x1d\x00\x00\x30\x00\xa1\x00\x00\x40\x00"; -// total size: 11 bytes - -/* -bits 32 +#define CODE_ADDRESS 0x100000 +#define READABLE_ADDRESS 0x300000 +#define WRITE_ONLY_ADDRESS 0x400000 - mov ebx, [0x300000] - mov eax, [0x400000] -*/ +static unsigned int protection_fault_count; -// callback for tracing memory access (READ or WRITE) -static bool hook_mem_invalid(uc_engine *uc, uc_mem_type type, - uint64_t address, int size, int64_t value, void *user_data) +static bool reject_protected_read(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, + void *user_data) { - - switch(type) { - default: - // return false to indicate we want to stop emulation - return false; - case UC_MEM_READ_PROT: - printf(">>> non-readable memory is being read at 0x%"PRIx64 ", data size = %u\n", - address, size); - return false; + (void)uc; + (void)value; + (void)user_data; + if (type == UC_MEM_READ_PROT && address == WRITE_ONLY_ADDRESS && + size == 4) { + protection_fault_count++; } + return false; } - -int main(int argc, char **argv, char **envp) +int main(void) { + /* mov ebx, [0x300000]; mov eax, [0x400000] */ + const uint8_t code[] = { + 0x8b, 0x1d, 0x00, 0x00, 0x30, 0x00, 0xa1, 0x00, 0x00, 0x40, 0x00, + }; + const uint32_t readable_value = 0x41414141; + const uint32_t protected_value = 0x42424242; + uint32_t eax = 0; + uint32_t ebx = 0; uc_engine *uc; - uc_hook trace1; + uc_hook hook; uc_err err; - uint32_t eax, ebx; - - printf("Memory protections test\n"); - // Initialize emulator in X86-32bit mode err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); - if (err) { - printf("Failed on uc_open() with error returned: %u\n", err); + if (err != UC_ERR_OK) { return 1; } - - uc_mem_map(uc, 0x100000, 0x1000, UC_PROT_READ); - uc_mem_map(uc, 0x300000, 0x1000, UC_PROT_READ | UC_PROT_WRITE); - uc_mem_map(uc, 0x400000, 0x1000, UC_PROT_WRITE); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, 0x100000, PROGRAM, sizeof(PROGRAM))) { - printf("Failed to write emulation code to memory, quit!\n"); - return 2; - } else { - printf("Allowed to write to read only memory via uc_mem_write\n"); + if (uc_mem_map(uc, CODE_ADDRESS, 0x1000, UC_PROT_READ | UC_PROT_EXEC) != + UC_ERR_OK || + uc_mem_map(uc, READABLE_ADDRESS, 0x1000, + UC_PROT_READ | UC_PROT_WRITE) != UC_ERR_OK || + uc_mem_map(uc, WRITE_ONLY_ADDRESS, 0x1000, UC_PROT_WRITE) != + UC_ERR_OK || + uc_mem_write(uc, CODE_ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_mem_write(uc, READABLE_ADDRESS, &readable_value, + sizeof(readable_value)) != UC_ERR_OK || + uc_mem_write(uc, WRITE_ONLY_ADDRESS, &protected_value, + sizeof(protected_value)) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_MEM_READ_PROT, reject_protected_read, + NULL, 1, 0) != UC_ERR_OK) { + uc_close(uc); + return 1; } - uc_mem_write(uc, 0x300000, (const uint8_t*)"\x41\x41\x41\x41", 4); - uc_mem_write(uc, 0x400000, (const uint8_t*)"\x42\x42\x42\x42", 4); - - //uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, 0x400000, 0x400fff); - - // intercept invalid memory events - uc_hook_add(uc, &trace1, UC_MEM_READ_PROT, hook_mem_invalid, NULL, 1, 0); - - // emulate machine code in infinite time - printf("BEGIN execution\n"); - err = uc_emu_start(uc, 0x100000, 0x100000 + sizeof(PROGRAM), 0, 2); - if (err) { - printf("Expected failure on uc_emu_start() with error returned %u: %s\n", - err, uc_strerror(err)); - } else { - printf("UNEXPECTED uc_emu_start returned UC_ERR_OK\n"); + err = uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_READ_PROT || + uc_reg_read(uc, UC_X86_REG_EAX, &eax) != UC_ERR_OK || + uc_reg_read(uc, UC_X86_REG_EBX, &ebx) != UC_ERR_OK) { + uc_close(uc); + return 1; } - printf("END execution\n"); - - uc_reg_read(uc, UC_X86_REG_EAX, &eax); - printf("Final eax = 0x%x\n", eax); - uc_reg_read(uc, UC_X86_REG_EBX, &ebx); - printf("Final ebx = 0x%x\n", ebx); - uc_close(uc); - - return 0; + if (uc_close(uc) != UC_ERR_OK) { + return 1; + } + return protection_fault_count == 1 && eax == 0 && ebx == readable_value ? 0 + : 1; } diff --git a/tests/regress/regress.sh b/tests/regress/regress.sh deleted file mode 100755 index 0c05699bad..0000000000 --- a/tests/regress/regress.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - - -./map_crash -./map_write -./sigill -./sigill2 -./block_test -./ro_mem_test -./nr_mem_test -./timeout_segfault -./rep_movsb -./mem_unmap -./mem_protect -./mem_exec -./mem_map_large -./00opcode_uc_crash -./eflags_noset -./eflags_nosync -./mips_kseg0_1 -./mem_double_unmap - diff --git a/tests/regress/rep_movsb.c b/tests/regress/rep_movsb.c index 436a02b928..ccbbbeb220 100644 --- a/tests/regress/rep_movsb.c +++ b/tests/regress/rep_movsb.c @@ -1,182 +1,144 @@ /* - -rep movsb regression - -Copyright(c) 2015 Chris Eagle - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -version 2 as published by the Free Software Foundation. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -*/ - -#define __STDC_FORMAT_MACROS + * rep movsb regression + * + * Copyright(c) 2015 Chris Eagle + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include +#include #include -#include -#include -#include #include -unsigned char PROGRAM[] = - "\xbe\x00\x00\x20\x00\xbf\x00\x10\x20\x00\xb9\x14\x00\x00\x00\xf3" - "\xa4\xf4"; -// total size: 18 bytes - -/* -bits 32 - -; assumes code section at 0x100000 r-x -; assumes data section at 0x200000-0x202000, rw- +#define CODE_ADDRESS UINT64_C(0x100000) +#define SOURCE_ADDRESS UINT32_C(0x200000) +#define DESTINATION_ADDRESS UINT32_C(0x201000) +#define COPY_SIZE 20 -mov esi, 0x200000 -mov edi, 0x201000 -mov ecx, 20 -rep movsb -hlt -*/ +typedef struct WriteState { + const uint8_t *expected; + size_t count; + bool failed; +} WriteState; -static int log_num = 1; - -// callback for tracing instruction -static void hook_code(uc_engine *uc, uint64_t addr, uint32_t size, void *user_data) +static void hook_mem_write(uc_engine *uc, uc_mem_type type, uint64_t address, + int size, int64_t value, void *user_data) { - uint8_t opcode; - if (uc_mem_read(uc, addr, &opcode, 1) != UC_ERR_OK) { - printf("not ok %d - uc_mem_read fail during hook_code callback, addr: 0x%" PRIx64 "\n", log_num++, addr); - _exit(-1); - } - switch (opcode) { - case 0xf4: //hlt - printf("# Handling HLT\n"); - if (uc_emu_stop(uc) != UC_ERR_OK) { - printf("not ok %d - uc_emu_stop fail during hook_code callback, addr: 0x%" PRIx64 "\n", log_num++, addr); - _exit(-1); - } - else { - printf("ok %d - hlt encountered, uc_emu_stop called\n", log_num++); - } - break; - default: //all others - break; - } + WriteState *state = user_data; + + if (type != UC_MEM_WRITE || state->count >= COPY_SIZE || + address != DESTINATION_ADDRESS + state->count || size != 1 || + (uint8_t)value != state->expected[state->count]) { + state->failed = true; + uc_emu_stop(uc); + return; + } + state->count++; } - -// callback for tracing memory access (READ or WRITE) -static void hook_mem_write(uc_engine *uc, uc_mem_type type, - uint64_t addr, int size, int64_t value, void *user_data) +int main(void) { - printf("# write to memory at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", addr, size, value); - if (addr < 0x201000L) { - //this is actually a read, we don't write in this range - printf("not ok %d - write hook called for read of 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", log_num++, addr, size, value); - } - else { - printf("ok %d - write hook called for write of 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", log_num++, addr, size, value); - } -} - -int main(int argc, char **argv, char **envp) -{ - uc_engine *uc; - uc_hook trace1, trace2; - uc_err err; - uint8_t buf1[100], readbuf[100]; - - printf("# rep movsb test\n"); - - memset(buf1, 'A', 20); - - // Initialize emulator in X86-32bit mode - err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); - if (err) { - printf("not ok %d - Failed on uc_open() with error returned: %u\n", log_num++, err); - return 1; - } - else { - printf("ok %d - uc_open() success\n", log_num++); - } - - uc_mem_map(uc, 0x100000, 0x1000, UC_PROT_READ); - uc_mem_map(uc, 0x200000, 0x2000, UC_PROT_READ | UC_PROT_WRITE); - - // fill in the data that we want to copy - if (uc_mem_write(uc, 0x200000, buf1, 20)) { - printf("not ok %d - Failed to write read buffer to memory, quit!\n", log_num++); - return 2; - } - else { - printf("ok %d - Read buffer written to memory\n", log_num++); - } - - // write machine code to be emulated to memory - if (uc_mem_write(uc, 0x100000, PROGRAM, sizeof(PROGRAM))) { - printf("not ok %d - Failed to write emulation code to memory, quit!\n", log_num++); - return 4; - } - else { - printf("ok %d - Program written to memory\n", log_num++); - } - - if (uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, 1, 0) != UC_ERR_OK) { - printf("not ok %d - Failed to install UC_HOOK_CODE handler\n", log_num++); - return 5; - } - else { - printf("ok %d - UC_HOOK_CODE installed\n", log_num++); - } - - // intercept memory write events only, NOT read events - if (uc_hook_add(uc, &trace1, UC_HOOK_MEM_WRITE, hook_mem_write, NULL, 1, 0) != UC_ERR_OK) { - printf("not ok %d - Failed to install UC_HOOK_MEM_WRITE handler\n", log_num++); - return 6; - } - else { - printf("ok %d - UC_HOOK_MEM_WRITE installed\n", log_num++); - } - - // emulate machine code until told to stop by hook_code - printf("# BEGIN execution\n"); - err = uc_emu_start(uc, 0x100000, 0x101000, 0, 0); - if (err != UC_ERR_OK) { - printf("not ok %d - Failure on uc_emu_start() with error %u:%s\n", log_num++, err, uc_strerror(err)); - return 8; - } - else { - printf("ok %d - uc_emu_start complete\n", log_num++); - } - printf("# END execution\n"); - - //make sure that data got copied - // fill in sections that shouldn't get touched - if (uc_mem_read(uc, 0x201000, readbuf, 20)) { - printf("not ok %d - Failed to read random buffer 1 from memory\n", log_num++); - } - else { - printf("ok %d - Random buffer 1 read from memory\n", log_num++); - if (memcmp(buf1, readbuf, 20)) { - printf("not ok %d - write buffer contents are incorrect\n", log_num++); - } - else { - printf("ok %d - write buffer contents are correct\n", log_num++); - } - } - - if (uc_close(uc) == UC_ERR_OK) { - printf("ok %d - uc_close complete\n", log_num++); - } - else { - printf("not ok %d - uc_close complete\n", log_num++); - } - - return 0; + const uint8_t code[] = { + 0xbe, 0x00, 0x00, 0x20, 0x00, /* mov esi, 0x200000 */ + 0xbf, 0x00, 0x10, 0x20, 0x00, /* mov edi, 0x201000 */ + 0xb9, 0x14, 0x00, 0x00, 0x00, /* mov ecx, 20 */ + 0xf3, 0xa4, /* rep movsb */ + }; + uint8_t source[COPY_SIZE]; + uint8_t destination[COPY_SIZE]; + uint32_t esi = 0; + uint32_t edi = 0; + uint32_t ecx = 0; + WriteState state = {source, 0, false}; + uc_engine *uc = NULL; + uc_hook hook; + uc_err err; + size_t i; + + for (i = 0; i < COPY_SIZE; i++) { + source[i] = (uint8_t)(0x40 + i); + } + + err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_open failed: %s\n", uc_strerror(err)); + return 1; + } + err = uc_mem_map(uc, CODE_ADDRESS, 0x1000, UC_PROT_READ | UC_PROT_EXEC); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_map(code) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_map(uc, SOURCE_ADDRESS, 0x2000, UC_PROT_READ | UC_PROT_WRITE); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_map(data) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_write(uc, CODE_ADDRESS, code, sizeof(code)); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_write(code) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_mem_write(uc, SOURCE_ADDRESS, source, sizeof(source)); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_write(source) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = + uc_hook_add(uc, &hook, UC_HOOK_MEM_WRITE, hook_mem_write, &state, 1, 0); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_hook_add failed: %s\n", uc_strerror(err)); + goto fail; + } + + err = uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_emu_start failed: %s\n", uc_strerror(err)); + goto fail; + } + err = + uc_mem_read(uc, DESTINATION_ADDRESS, destination, sizeof(destination)); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_read failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_reg_read(uc, UC_X86_REG_ESI, &esi); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_read(ESI) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_reg_read(uc, UC_X86_REG_EDI, &edi); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_read(EDI) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_reg_read(uc, UC_X86_REG_ECX, &ecx); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_read(ECX) failed: %s\n", uc_strerror(err)); + goto fail; + } + if (state.failed || state.count != COPY_SIZE || + memcmp(destination, source, sizeof(source)) != 0 || + esi != SOURCE_ADDRESS + COPY_SIZE || + edi != DESTINATION_ADDRESS + COPY_SIZE || ecx != 0) { + fprintf(stderr, + "unexpected REP result: hooks=%zu failed=%d " + "ESI=0x%x EDI=0x%x ECX=%u\n", + state.count, state.failed, esi, edi, ecx); + goto fail; + } + + err = uc_close(uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_close failed: %s\n", uc_strerror(err)); + return 1; + } + return 0; + +fail: + uc_close(uc); + return 1; } diff --git a/tests/regress/ro_mem_test.c b/tests/regress/ro_mem_test.c index 0197877b76..1412919259 100644 --- a/tests/regress/ro_mem_test.c +++ b/tests/regress/ro_mem_test.c @@ -1,209 +1,189 @@ /* -Non-writable memory test case - -Copyright(c) 2015 Chris Eagle - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -version 2 as published by the Free Software Foundation. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -*/ - + * Non-writable memory test case + * + * Copyright(c) 2015 Chris Eagle + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include +#include #include #include -const uint8_t PROGRAM[] = - "\xeb\x1a\x58\x83\xc0\x04\x83\xe0\xfc\x83\xc0\x01\xc7\x00\x78\x56" - "\x34\x12\x83\xc0\x07\xc7\x00\x21\x43\x65\x87\x90\xe8\xe1\xff\xff" - "\xff" "xxxxAAAAxxxBBBB"; -// total size: 33 bytes - -/* - jmp short bottom -top: - pop eax - add eax, 4 - and eax, 0xfffffffc - add eax, 1 ; unaligned - mov dword [eax], 0x12345678 ; try to write into code section - add eax, 7 ; aligned - mov dword [eax], 0x87654321 ; try to write into code section - nop -bottom: - call top -*/ - -// callback for tracing instruction -/*static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) -{ - uint32_t esp; - printf(">>> Tracing instruction at 0x%"PRIx64 ", instruction size = 0x%x\n", address, size); - - uc_reg_read(uc, UC_X86_REG_ESP, &esp); - printf(">>> --- ESP is 0x%x\n", esp); - -} -*/ - -// callback for tracing memory access (READ or WRITE) -static bool hook_mem_invalid(uc_engine *uc, uc_mem_type type, - uint64_t address, int size, int64_t value, void *user_data) +#define CODE_ADDRESS UINT64_C(0x400000) +#define STACK_ADDRESS UINT64_C(0x500000) +#define STACK_SIZE UINT64_C(0x5000) +#define STACK_TOP (STACK_ADDRESS + STACK_SIZE) +#define UNALIGNED_WRITE_ADDRESS (CODE_ADDRESS + UINT64_C(0x25)) +#define ALIGNED_WRITE_ADDRESS (CODE_ADDRESS + UINT64_C(0x2c)) + +typedef struct WriteFaultState { + unsigned int stack_maps; + unsigned int protected_writes; + bool failed; +} WriteFaultState; + +static bool hook_mem_invalid(uc_engine *uc, uc_mem_type type, uint64_t address, + int size, int64_t value, void *user_data) { - uint32_t esp; - uc_reg_read(uc, UC_X86_REG_ESP, &esp); + const uint64_t protected_addresses[] = { + UNALIGNED_WRITE_ADDRESS, + ALIGNED_WRITE_ADDRESS, + }; + const uint32_t protected_values[] = { + UINT32_C(0x12345678), + UINT32_C(0x87654321), + }; + WriteFaultState *state = user_data; + uc_err err; - switch(type) { - default: - // return false to indicate we want to stop emulation + if (type == UC_MEM_WRITE_UNMAPPED) { + if (state->stack_maps != 0 || address != STACK_TOP - 4 || size != 4 || + (uint32_t)value != CODE_ADDRESS + 0x21) { + state->failed = true; return false; - case UC_MEM_WRITE: - //if this is a push, esp has not been adjusted yet - if (esp == (address + size)) { - uint32_t upper; - upper = (esp + 0xfff) & ~0xfff; - printf(">>> Stack appears to be missing at 0x%"PRIx64 ", allocating now\n", address); - // map this memory in with 2MB in size - uc_mem_map(uc, upper - 0x8000, 0x8000, UC_PROT_READ | UC_PROT_WRITE); - // return true to indicate we want to continue - return true; - } - printf(">>> Missing memory is being WRITTEN at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", - address, size, value); - return false; - case UC_MEM_WRITE_PROT: - printf(">>> RO memory is being WRITTEN at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", - address, size, value); + } + err = uc_mem_map(uc, STACK_ADDRESS, STACK_SIZE, + UC_PROT_READ | UC_PROT_WRITE); + if (err != UC_ERR_OK) { + state->failed = true; return false; + } + state->stack_maps++; + return true; } -} - -#define STACK 0x500000 -#define STACK_SIZE 0x5000 + if (type == UC_MEM_WRITE_PROT) { + if (state->protected_writes >= 2 || + address != protected_addresses[state->protected_writes] || + size != 4 || + (uint32_t)value != protected_values[state->protected_writes]) { + state->failed = true; + } + state->protected_writes++; + return false; + } -int main(int argc, char **argv, char **envp) + state->failed = true; + return false; +} +int main(void) { - uc_engine *uc; - uc_hook trace1; + const uint8_t code[] = { + 0xeb, 0x1a, /* jmp bottom */ + 0x58, /* pop eax */ + 0x83, 0xc0, 0x04, /* add eax, 4 */ + 0x83, 0xe0, 0xfc, /* and eax, -4 */ + 0x83, 0xc0, 0x01, /* add eax, 1 */ + 0xc7, 0x00, 0x78, 0x56, 0x34, 0x12, /* unaligned write */ + 0x83, 0xc0, 0x07, /* add eax, 7 */ + 0xc7, 0x00, 0x21, 0x43, 0x65, 0x87, /* aligned write */ + 0x90, /* nop */ + 0xe8, 0xe1, 0xff, 0xff, 0xff, /* call top */ + 'x', 'x', 'x', 'x', 'A', 'A', 'A', 'A', + 'x', 'x', 'x', 'B', 'B', 'B', 'B', + }; + const uint8_t expected_stack[] = {0x21, 0x00, 0x40, 0x00}; + const uint8_t expected_a[] = {'A', 'A', 'A', 'A'}; + const uint8_t expected_b[] = {'B', 'B', 'B', 'B'}; + uint8_t actual[4]; + uint32_t esp = (uint32_t)STACK_TOP; + uint32_t eax = (uint32_t)ALIGNED_WRITE_ADDRESS; + WriteFaultState state = {0}; + uc_engine *uc = NULL; + uc_hook hook; uc_err err; - uint8_t bytes[8]; - uint32_t esp; - int map_stack = 0; - - if (argc == 2 && strcmp(argv[1], "--map-stack") == 0) { - map_stack = 1; - } - - printf("Memory mapping test\n"); - // Initialize emulator in X86-32bit mode err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); - if (err) { - printf("Failed on uc_open() with error returned: %u\n", err); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_open failed: %s\n", uc_strerror(err)); return 1; } - - uc_mem_map(uc, 0x100000, 0x1000, UC_PROT_ALL); - uc_mem_map(uc, 0x200000, 0x2000, UC_PROT_ALL); - uc_mem_map(uc, 0x300000, 0x3000, UC_PROT_ALL); - uc_mem_map(uc, 0x400000, 0x4000, UC_PROT_READ); - - if (map_stack) { - printf("Pre-mapping stack\n"); - uc_mem_map(uc, STACK, STACK_SIZE, UC_PROT_READ | UC_PROT_WRITE); - } else { - printf("Mapping stack on first invalid memory access\n"); + err = uc_mem_map(uc, CODE_ADDRESS, 0x1000, UC_PROT_READ | UC_PROT_EXEC); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_map(code) failed: %s\n", uc_strerror(err)); + goto fail; } - - esp = STACK + STACK_SIZE; - - uc_reg_write(uc, UC_X86_REG_ESP, &esp); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, 0x400000, PROGRAM, sizeof(PROGRAM))) { - printf("Failed to write emulation code to memory, quit!\n"); - return 2; - } else { - printf("Allowed to write to read only memory via uc_mem_write\n"); + err = uc_mem_write(uc, CODE_ADDRESS, code, sizeof(code)); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_mem_write(code) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_reg_write(uc, UC_X86_REG_ESP, &esp); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_write(ESP) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_hook_add(uc, &hook, + UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_WRITE_PROT, + hook_mem_invalid, &state, 1, 0); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_hook_add failed: %s\n", uc_strerror(err)); + goto fail; } - //uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, 0x400000, 0x400fff); - - // intercept invalid memory events - uc_hook_add(uc, &trace1, UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_WRITE_PROT, hook_mem_invalid, NULL, 1, 0); - - // emulate machine code in infinite time - printf("BEGIN execution - 1\n"); - err = uc_emu_start(uc, 0x400000, 0x400000 + sizeof(PROGRAM), 0, 10); - if (err) { - printf("Expected failue on uc_emu_start() with error returned %u: %s\n", - err, uc_strerror(err)); - } else { - printf("UNEXPECTED uc_emu_start returned UC_ERR_OK\n"); - } - printf("END execution - 1\n"); - - // emulate machine code in infinite time - printf("BEGIN execution - 2\n"); - //update eax to point to aligned memory (same as add eax,7 above) - uint32_t eax = 0x40002C; - uc_reg_write(uc, UC_X86_REG_EAX, &eax); - //resume execution at the mov dword [eax], 0x87654321 - //to test an aligned write as well - err = uc_emu_start(uc, 0x400015, 0x400000 + sizeof(PROGRAM), 0, 2); - if (err) { - printf("Expected failure on uc_emu_start() with error returned %u: %s\n", - err, uc_strerror(err)); - } else { - printf("UNEXPECTED uc_emu_start returned UC_ERR_OK\n"); - } - printf("END execution - 2\n"); + err = uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + sizeof(code), 0, 10); + if (err != UC_ERR_WRITE_PROT) { + fprintf(stderr, "first write expected UC_ERR_WRITE_PROT, got %s\n", + uc_strerror(err)); + goto fail; + } + if (state.failed || state.stack_maps != 1 || state.protected_writes != 1) { + fprintf(stderr, + "unexpected first fault state: maps=%u protected=%u " + "failed=%d\n", + state.stack_maps, state.protected_writes, state.failed); + goto fail; + } - printf("Verifying content at 0x400025 is unchanged\n"); - if (!uc_mem_read(uc, 0x400025, bytes, 4)) { - printf(">>> Read 4 bytes from [0x%x] = 0x%x\n", (uint32_t)0x400025, *(uint32_t*) bytes); - if (0x41414141 != *(uint32_t*) bytes) { - printf("ERROR content in read only memory changed\n"); - } else { - printf("SUCCESS content in read only memory unchanged\n"); - } - } else { - printf(">>> Failed to read 4 bytes from [0x%x]\n", (uint32_t)(esp - 4)); - return 4; + err = uc_reg_write(uc, UC_X86_REG_EAX, &eax); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_reg_write(EAX) failed: %s\n", uc_strerror(err)); + goto fail; + } + err = uc_emu_start(uc, CODE_ADDRESS + 0x15, CODE_ADDRESS + sizeof(code), 0, + 1); + if (err != UC_ERR_WRITE_PROT) { + fprintf(stderr, "second write expected UC_ERR_WRITE_PROT, got %s\n", + uc_strerror(err)); + goto fail; + } + if (state.failed || state.stack_maps != 1 || state.protected_writes != 2) { + fprintf(stderr, + "unexpected second fault state: maps=%u protected=%u " + "failed=%d\n", + state.stack_maps, state.protected_writes, state.failed); + goto fail; } - printf("Verifying content at 0x40002C is unchanged\n"); - if (!uc_mem_read(uc, 0x40002C, bytes, 4)) { - printf(">>> Read 4 bytes from [0x%x] = 0x%x\n", (uint32_t)0x40002C, *(uint32_t*) bytes); - if (0x42424242 != *(uint32_t*) bytes) { - printf("ERROR content in read only memory changed\n"); - } else { - printf("SUCCESS content in read only memory unchanged\n"); - } - } else { - printf(">>> Failed to read 4 bytes from [0x%x]\n", (uint32_t)(esp - 4)); - return 4; + err = uc_mem_read(uc, STACK_TOP - 4, actual, sizeof(actual)); + if (err != UC_ERR_OK || memcmp(actual, expected_stack, sizeof(actual))) { + fprintf(stderr, "stack return address is incorrect\n"); + goto fail; + } + err = uc_mem_read(uc, UNALIGNED_WRITE_ADDRESS, actual, sizeof(actual)); + if (err != UC_ERR_OK || memcmp(actual, expected_a, sizeof(actual))) { + fprintf(stderr, "unaligned protected write changed memory\n"); + goto fail; + } + err = uc_mem_read(uc, ALIGNED_WRITE_ADDRESS, actual, sizeof(actual)); + if (err != UC_ERR_OK || memcmp(actual, expected_b, sizeof(actual))) { + fprintf(stderr, "aligned protected write changed memory\n"); + goto fail; } - printf("Verifying content at bottom of stack is readable and correct\n"); - if (!uc_mem_read(uc, esp - 4, bytes, 4)) { - printf(">>> Read 4 bytes from [0x%x] = 0x%x\n", (uint32_t)(esp - 4), *(uint32_t*) bytes); - } else { - printf(">>> Failed to read 4 bytes from [0x%x]\n", (uint32_t)(esp - 4)); - return 4; + err = uc_close(uc); + if (err != UC_ERR_OK) { + fprintf(stderr, "uc_close failed: %s\n", uc_strerror(err)); + return 1; } + return 0; +fail: uc_close(uc); - - return 0; + return 1; } diff --git a/tests/regress/runner/run_python_regression.py b/tests/regress/runner/run_python_regression.py new file mode 100644 index 0000000000..23db8ddf0a --- /dev/null +++ b/tests/regress/runner/run_python_regression.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +import argparse +import os +from pathlib import Path +import subprocess +import sys + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--binding", required=True) + parser.add_argument("--library", required=True) + parser.add_argument("--test", required=True) + parser.add_argument("--timeout", required=True, type=int) + args = parser.parse_args() + + binding = Path(args.binding).resolve() + library = Path(args.library).resolve() + test = Path(args.test).resolve() + + env = os.environ.copy() + library_dir = str(library.parent) + env["LIBUNICORN_PATH"] = library_dir + env["PYTHONPATH"] = os.pathsep.join( + (str(binding), str(test.parent), env.get("PYTHONPATH", "")) + ).rstrip(os.pathsep) + env["PATH"] = os.pathsep.join( + (library_dir, env.get("PATH", "")) + ).rstrip(os.pathsep) + if sys.platform == "darwin": + env["DYLD_LIBRARY_PATH"] = os.pathsep.join( + (library_dir, env.get("DYLD_LIBRARY_PATH", "")) + ).rstrip(os.pathsep) + elif os.name != "nt": + env["LD_LIBRARY_PATH"] = os.pathsep.join( + (library_dir, env.get("LD_LIBRARY_PATH", "")) + ).rstrip(os.pathsep) + + probe = subprocess.run( + [ + sys.executable, + "-c", + "import ctypes, sys; ctypes.CDLL(sys.argv[1])", + str(library), + ], + env=env, + timeout=10, + check=False, + ) + if probe.returncode != 0: + return probe.returncode + + try: + completed = subprocess.run( + [sys.executable, str(test)], + cwd=str(test.parent), + env=env, + timeout=args.timeout, + check=False, + ) + except subprocess.TimeoutExpired: + print( + "Python regression timed out after {} seconds: {}".format( + args.timeout, test.name + ), + file=sys.stderr, + ) + return 124 + + return completed.returncode + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/regress/rw_hookstack.c b/tests/regress/rw_hookstack.c index 73dcc64b8d..96e1635e4e 100644 --- a/tests/regress/rw_hookstack.c +++ b/tests/regress/rw_hookstack.c @@ -1,106 +1,75 @@ -#include -#include #include -#define ADDRESS 0x1000000 -#define STACK 0x0020D000 -#define STACK2 0x0030D000 -#define STACK_SIZE 16384 -#define SIZE (2 * 1024 * 1024) -#define CODE32 "\x8B\x04\x24\xA3\x40\x00\x00\x01\xA1\x40\x00\x00\x01" +#define CODE_ADDRESS 0x1000000 +#define RESULT_ADDRESS (CODE_ADDRESS + 0x40) -bool hook_mem_rw(uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, void *user_data) -{ - unsigned int EIP; - - uc_reg_read(uc, UC_X86_REG_EIP, &EIP); - switch(type) - { - default: - return false; - break; - case UC_MEM_WRITE: - printf("Hooked write to address 0x%08"PRIX64" with value 0x%08"PRIX64" at EIP %08X\n", address, value, EIP); +typedef struct HookState { + unsigned int reads; + unsigned int writes; +} HookState; - return true; - break; - case UC_MEM_READ: - printf("Hooked read from address 0x%08"PRIX64" with value 0x%08"PRIX64" at EIP %08X\n", address, value, EIP); - - return true; - break; +static void count_memory_access(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, + void *user_data) +{ + HookState *state = user_data; + + (void)uc; + (void)address; + (void)size; + (void)value; + if (type == UC_MEM_READ) { + state->reads++; + } else if (type == UC_MEM_WRITE) { + state->writes++; } } -int main(int argc, char *argv[]) +static bool run_stack_case(uc_engine *uc, uint32_t stack_address, + uint32_t value) { - uc_engine *uc; - uc_hook trace; - uc_err err; - unsigned int EAX, ESP, val = 0x0c0c0c0c, stkval = STACK; - - EAX = 0; - ESP = STACK+0x4; - - // Initialize emulator in X86-64bit mode - err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); - if(err) { - printf("Failed on uc_open() with error returned: %s\n", uc_strerror(err)); - return 1; - } - - err = uc_mem_map(uc, ADDRESS, SIZE, UC_PROT_ALL); - if(err != UC_ERR_OK) { - printf("Failed to map memory %s\n", uc_strerror(err)); - return 1; - } - - err = uc_mem_write(uc, ADDRESS, CODE32, sizeof(CODE32) - 1); - if(err != UC_ERR_OK) { - printf("Failed to write to memory %s\n", uc_strerror(err)); - return 1; + uint32_t eax = 0; + uint32_t esp = stack_address + 4; + uint32_t result = 0; + + if (uc_mem_map(uc, stack_address, 0x4000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, esp, &value, sizeof(value)) != UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_EAX, &eax) != UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_ESP, &esp) != UC_ERR_OK || + uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + 13, 0, 0) != UC_ERR_OK || + uc_reg_read(uc, UC_X86_REG_EAX, &eax) != UC_ERR_OK || + uc_mem_read(uc, RESULT_ADDRESS, &result, sizeof(result)) != UC_ERR_OK) { + return false; } + return eax == value && result == value; +} -loop: - err = uc_mem_map(uc, stkval, STACK_SIZE, UC_PROT_ALL); - if(err != UC_ERR_OK) { - printf("Failed to map memory %s\n", uc_strerror(err)); - return 1; - } +int main(void) +{ + const uint8_t code[] = { + 0x8b, 0x04, 0x24, /* mov eax, [esp] */ + 0xa3, 0x40, 0x00, 0x00, 0x01, /* mov [result], eax */ + 0xa1, 0x40, 0x00, 0x00, 0x01, /* mov eax, [result] */ + }; + HookState state = {0}; + uc_engine *uc; + uc_hook hook; - err = uc_mem_write(uc, ESP, &val, sizeof(val)); - if(err != UC_ERR_OK) { - printf("Failed to write to memory %s\n", uc_strerror(err)); + if (uc_open(UC_ARCH_X86, UC_MODE_32, &uc) != UC_ERR_OK) { return 1; } - - - uc_hook_add(uc, &trace, UC_HOOK_MEM_WRITE | UC_HOOK_MEM_READ, (void *)hook_mem_rw, NULL, 1, 0); - - uc_reg_write(uc, UC_X86_REG_EAX, &EAX); - uc_reg_write(uc, UC_X86_REG_ESP, &ESP); - - err = uc_emu_start(uc, ADDRESS, ADDRESS + (sizeof(CODE32) - 1), 0, 0); - if(err) { - printf("Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - + if (uc_mem_map(uc, CODE_ADDRESS, 0x200000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, CODE_ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, + count_memory_access, &state, 1, 0) != UC_ERR_OK || + !run_stack_case(uc, 0x20d000, 0x0c0c0c0c) || + !run_stack_case(uc, 0x30d000, 0xa5a5a5a5)) { uc_close(uc); return 1; } - uc_reg_read(uc, UC_X86_REG_EAX, &EAX); - - printf(">>> EAX = %08X\n", EAX); - - if(stkval != STACK2) - { - printf("=== Beginning test two ===\n"); - ESP = STACK2+0x4; - EAX = 0; - stkval = STACK2; - goto loop; + if (uc_close(uc) != UC_ERR_OK) { + return 1; } - - uc_close(uc); - return 0; + return state.reads == 4 && state.writes == 2 ? 0 : 1; } diff --git a/tests/regress/sigill.c b/tests/regress/sigill.c index d92bfa6e0c..9a42f1046f 100644 --- a/tests/regress/sigill.c +++ b/tests/regress/sigill.c @@ -1,47 +1,39 @@ #include -#include -#include -#include -#define UC_BUG_WRITE_SIZE 128 -#define UC_BUG_WRITE_ADDR 0x1000 // fix this by change this to 0x2000 +#define ADDRESS 0x1000 -int got_sigill = 0; +static unsigned int invalid_instruction_count; -void _interrupt(uc_engine *uc, uint32_t intno, void *user_data) +static bool count_invalid_instruction(uc_engine *uc, void *user_data) { - if (intno == 6) { - uc_emu_stop(uc); - got_sigill = 1; - } + (void)uc; + (void)user_data; + invalid_instruction_count++; + return false; } int main(void) { - int size; - uint8_t *buf; + const uint8_t code[] = {0x0f, 0x0b}; /* ud2 */ uc_engine *uc; - uc_hook uh_trap; - uc_err err = uc_open (UC_ARCH_X86, UC_MODE_64, &uc); - if (err) { - fprintf (stderr, "Cannot initialize unicorn\n"); + uc_hook hook; + uc_err err; + + err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc); + if (err != UC_ERR_OK) { return 1; } - size = UC_BUG_WRITE_SIZE; - buf = malloc (size); - if (!buf) { - fprintf (stderr, "Cannot allocate\n"); + if (uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_INSN_INVALID, count_invalid_instruction, + NULL, 1, 0) != UC_ERR_OK) { + uc_close(uc); return 1; } - memset (buf, 0, size); - if (!uc_mem_map(uc, UC_BUG_WRITE_ADDR, size, UC_PROT_ALL)) { - uc_mem_write(uc, UC_BUG_WRITE_ADDR, - (const uint8_t*)"\xff\xff\xff\xff\xff\xff\xff\xff", 8); + + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 1); + if (uc_close(uc) != UC_ERR_OK) { + return 1; } - uc_hook_add(uc, &uh_trap, UC_HOOK_INTR, _interrupt, NULL, 1, 0); - uc_emu_start(uc, UC_BUG_WRITE_ADDR, UC_BUG_WRITE_ADDR+8, 0, 1); - uc_close(uc); - free(buf); - printf ("Correct: %s\n", got_sigill? "YES": "NO"); - return got_sigill? 0: 1; + return err == UC_ERR_INSN_INVALID && invalid_instruction_count == 1 ? 0 : 1; } diff --git a/tests/regress/sigill2.c b/tests/regress/sigill2.c index 5a56171b8a..3ee95b2515 100644 --- a/tests/regress/sigill2.c +++ b/tests/regress/sigill2.c @@ -1,28 +1,26 @@ #include -#include -#include -#include -#define UC_BUG_WRITE_SIZE 128 -#define UC_BUG_WRITE_ADDR 0x2000 +#define ADDRESS 0x2000 int main(void) { - int size; + const uint8_t code[] = {0x0f, 0x0b}; /* ud2 */ uc_engine *uc; + uc_err err; - uc_err err = uc_open (UC_ARCH_X86, UC_MODE_64, &uc); - if (err) { - fprintf (stderr, "Cannot initialize unicorn\n"); + err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc); + if (err != UC_ERR_OK) { return 1; } - size = UC_BUG_WRITE_SIZE; - if (!uc_mem_map (uc, UC_BUG_WRITE_ADDR, size, UC_PROT_ALL)) { - uc_mem_write (uc, UC_BUG_WRITE_ADDR, - (const uint8_t*)"\xff\xff\xff\xff\xff\xff\xff\xff", 8); + if (uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 1); + if (uc_close(uc) != UC_ERR_OK) { + return 1; } - err = uc_emu_start(uc, UC_BUG_WRITE_ADDR, UC_BUG_WRITE_ADDR+8, 0, 1); - uc_close(uc); - printf ("Error = %u (%s)\n", err, uc_strerror(err)); - return err? -1: 0; + return err == UC_ERR_INSN_INVALID ? 0 : 1; } diff --git a/tests/regress/sparc_jump_to_zero.c b/tests/regress/sparc_jump_to_zero.c index 538405f3a5..cc7f27100e 100644 --- a/tests/regress/sparc_jump_to_zero.c +++ b/tests/regress/sparc_jump_to_zero.c @@ -1,27 +1,38 @@ #include -#define HARDWARE_ARCHITECTURE UC_ARCH_SPARC -#define HARDWARE_MODE UC_MODE_SPARC32|UC_MODE_BIG_ENDIAN +#define ADDRESS 0x100000 -#define MEMORY_STARTING_ADDRESS 0x1000000 -#define MEMORY_SIZE 2 * 1024 * 1024 -#define MEMORY_PERMISSIONS UC_PROT_ALL +int main(void) +{ + const uint8_t code[] = { + 0x02, 0xbc, 0x00, 0x00, /* be 0 */ + 0x01, 0x00, 0x00, 0x00, /* nop (delay slot) */ + }; + uint32_t pc = UINT32_MAX; + uint32_t psr = 1U << 22; /* ICC.Z */ + uc_engine *uc; + uc_err err; -#define BINARY_CODE "\x02\xbc\x00\x00" + err = uc_open(UC_ARCH_SPARC, UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN, &uc); + if (err != UC_ERR_OK) { + return 1; + } + if (uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_reg_write(uc, UC_SPARC_REG_PSR, &psr) != UC_ERR_OK) { + uc_close(uc); + return 1; + } -int main(int argc, char **argv, char **envp) { - uc_engine *uc; - if (uc_open(HARDWARE_ARCHITECTURE, HARDWARE_MODE, &uc)) { - printf("uc_open(…) failed\n"); - return 1; - } - uc_mem_map(uc, MEMORY_STARTING_ADDRESS, MEMORY_SIZE, MEMORY_PERMISSIONS); - if (uc_mem_write(uc, MEMORY_STARTING_ADDRESS, BINARY_CODE, sizeof(BINARY_CODE) - 1)) { - printf("uc_mem_write(…) failed\n"); - return 1; - } - printf("uc_emu_start(…)\n"); - uc_emu_start(uc, MEMORY_STARTING_ADDRESS, MEMORY_STARTING_ADDRESS + sizeof(BINARY_CODE) - 1, 0, 20); - printf("done\n"); - return 0; + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0); + if (err != UC_ERR_FETCH_UNMAPPED || + uc_reg_read(uc, UC_SPARC_REG_PC, &pc) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + + if (uc_close(uc) != UC_ERR_OK) { + return 1; + } + return pc == 0 ? 0 : 1; } diff --git a/tests/regress/sysenter_hook_x86.c b/tests/regress/sysenter_hook_x86.c index 7cf7b7c9b7..dbafc36423 100644 --- a/tests/regress/sysenter_hook_x86.c +++ b/tests/regress/sysenter_hook_x86.c @@ -1,60 +1,40 @@ #include -// code to be emulated -#define X86_CODE32 "\x0F\x34" // SYSENTER - -// memory address where emulation starts #define ADDRESS 0x1000000 -int got_sysenter = 0; +static unsigned int hook_count; +static uc_err stop_error; -void sysenter (uc_engine *uc, void *user) { - printf ("SYSENTER hook called.\n"); - got_sysenter = 1; +static void stop_on_sysenter(uc_engine *uc, void *user_data) +{ + (void)user_data; + hook_count++; + stop_error = uc_emu_stop(uc); } -int main(int argc, char **argv, char **envp) +int main(void) { - uc_engine *uc; - uc_err err; - uc_hook sysenterHook; - - // Initialize emulator in X86-32bit mode - err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); - if (err != UC_ERR_OK) { - printf("Failed on uc_open() with error returned: %u\n", err); - return -1; - } - - // map 2MB memory for this emulation - uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - if (uc_mem_write(uc, ADDRESS, X86_CODE32, sizeof(X86_CODE32) - 1)) { - printf("Failed to write emulation code to memory, quit!\n"); - return -1; - } - - // Hook the SYSENTER instructions - if (uc_hook_add (uc, &sysenterHook, UC_HOOK_INSN, sysenter, NULL, 1, 0, UC_X86_INS_SYSENTER) != UC_ERR_OK) { - printf ("Cannot hook SYSENTER instruction\n."); - return -1; - } - - // emulate code in infinite time & unlimited instructions - err=uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(X86_CODE32) - 1, 0, 0); - if (err) { - printf("Failed on uc_emu_start() with error returned %u: %s\n", - err, uc_strerror(err)); - } - - printf("Emulation done.\n"); - uc_close(uc); - - if (!got_sysenter) { - printf ("[!] ERROR : SYSENTER hook not called.\n"); - return -1; - } - - return 0; + const uint8_t code[] = {0x0f, 0x34}; /* sysenter */ + uc_engine *uc; + uc_hook hook; + uc_err err; + + err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); + if (err != UC_ERR_OK) { + return 1; + } + if (uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_hook_add(uc, &hook, UC_HOOK_INSN, stop_on_sysenter, NULL, 1, 0, + UC_X86_INS_SYSENTER) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0); + if (uc_close(uc) != UC_ERR_OK) { + return 1; + } + return err == UC_ERR_OK && stop_error == UC_ERR_OK && hook_count == 1 ? 0 + : 1; } diff --git a/tests/regress/tcg_liveness_analysis_bug_issue-287.py b/tests/regress/tcg_liveness_analysis_bug_issue_287.py similarity index 100% rename from tests/regress/tcg_liveness_analysis_bug_issue-287.py rename to tests/regress/tcg_liveness_analysis_bug_issue_287.py diff --git a/tests/regress/threaded_emu_start.c b/tests/regress/threaded_emu_start.c index 9301803dae..2b5424de72 100644 --- a/tests/regress/threaded_emu_start.c +++ b/tests/regress/threaded_emu_start.c @@ -1,227 +1,591 @@ -/* -Test for uc_open() and uc_emu_start() being called by different threads. - -This code will call uc_open() in the main thread and then attempt -to call uc_emu_start() from its own thread. This would enable the emulator -to run in the background while you do other things like handle user interface -etc in the foreground. - -Currently "uc->qemu_global_mutex" is locked by uc_open() and unlocked -by uc_emu_start(). This is a problem because the mutex implementation -must be locked and unlocked by the same thread. This means that uc_open() -and uc_emu_start() must be executed in the same thread. This is an unnecessary -limitation which prevents the emulator from being able to be executed in the -background. -*/ - -// windows specific -#ifdef _MSC_VER -#include -#include -#include -#define PRIx64 "llX" -#include -#ifdef _WIN64 -#pragma comment(lib, "unicorn_staload64.lib") -#else // _WIN64 -#pragma comment(lib, "unicorn_staload.lib") -#endif // _WIN64 - -// posix specific -#else // _MSC_VER -#include -#include "pthread.h" -#endif // _MSC_VER +#include +#include +#include +#include +#include +#include +#include -// for win32 threads in mingw #ifdef _WIN32 +#include #include +typedef DWORD SystemError; +#else +#include +#include +#include +typedef int SystemError; #endif -// common includes -#include +#include +#define STOP_STRESS_ITERATIONS 256 +#define STOP_COALESCE_CALLS 32 +#define WAIT_TIMEOUT_MS 10000 +#define EXTERNAL_RUN_TIMEOUT_US 20000000 +#define TIMER_STOP_TIMEOUT_US 1000 -// Test MIPS little endian code. -// This should loop forever. -const uint64_t addr = 0x100000; -const unsigned char loop_test_code[] = { - 0x02,0x00,0x04,0x24, // 100000: li $a0, 2 - // loop1 - 0x00,0x00,0x00,0x00, // 100004: nop - 0xFE,0xFF,0x80,0x14, // 100008: bnez $a0, loop1 - 0x00,0x00,0x00,0x00, // 10000C: nop +static const uint64_t code_address = 0x100000; +static const uint8_t code[] = { + 0x01, 0x00, 0x00, 0x10, /* beq $zero,$zero,code_address + 8 */ + 0x01, 0x00, 0x42, 0x24, /* addiu $v0,$v0,1 */ + 0x01, 0x00, 0x63, 0x24, /* addiu $v1,$v1,1 */ }; -bool test_passed_ok = false; -int loop_count = 0; +typedef struct StartSignal { +#ifdef _WIN32 + HANDLE event; + volatile LONG started; +#else + pthread_mutex_t mutex; + pthread_cond_t condition; + bool started; +#endif +} StartSignal; + +typedef enum StopSource { + STOP_FROM_EXTERNAL_THREAD, + STOP_FROM_TIMEOUT_THREAD, +} StopSource; + +typedef struct StopContext { + uc_engine *uc; + StopSource source; + uc_err emu_error; + uc_err stop_error; + uc_err query_error; + StartSignal entered_signal; + StartSignal stop_done_signal; + SystemError signal_error; + SystemError wait_error; + size_t timed_out; + uint32_t calls; + uint32_t stop_calls; +} StopContext; -// This hook is used to show that code is executing in the emulator. -static void mips_codehook(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) +#ifdef _WIN32 +static SystemError start_signal_init(StartSignal *signal) { - printf("Code: %"PRIx64"\n", address); + signal->started = 0; + signal->event = CreateEventW(NULL, TRUE, FALSE, NULL); + return signal->event == NULL ? GetLastError() : ERROR_SUCCESS; } +static SystemError start_signal_set(StartSignal *signal) +{ + if (InterlockedCompareExchange(&signal->started, 1, 0) == 0 && + !SetEvent(signal->event)) { + return GetLastError(); + } + return ERROR_SUCCESS; +} -typedef struct { - uc_engine *uc; - uint64_t startAddr; - uint64_t endAddr; -} EmuStarterParam_t; +static SystemError start_signal_wait(StartSignal *signal) +{ + DWORD result = WaitForSingleObject(signal->event, WAIT_TIMEOUT_MS); + + if (result == WAIT_OBJECT_0) { + return ERROR_SUCCESS; + } + if (result == WAIT_TIMEOUT) { + return ERROR_TIMEOUT; + } + return GetLastError(); +} -// This is a thread that just runs uc_emu_start() in it. -// The code that it is executing in this case will run forever until it is stopped by uc_emu_stop(). -static uc_err emu_starter(void* param) +static SystemError start_signal_destroy(StartSignal *signal) { - uc_engine *uc; - uint64_t start_addr; - uint64_t end_addr; - uc_err err; - - EmuStarterParam_t* starter_params = (EmuStarterParam_t *)param; - uc = starter_params->uc; - start_addr = starter_params->startAddr; - end_addr = starter_params->endAddr; - - printf("uc_emu_start()\n"); - err = uc_emu_start(uc, start_addr, end_addr, 0, 0); - if (err) - { - printf("Failed on uc_emu_start() with error returned %u: %s\n", - err, uc_strerror(err)); - } - - return err; + return CloseHandle(signal->event) ? ERROR_SUCCESS : GetLastError(); +} + +static SystemError timeout_error(void) +{ + return ERROR_TIMEOUT; +} + +static void sleep_one_millisecond(void) +{ + Sleep(1); +} +#else +static SystemError start_signal_init(StartSignal *signal) +{ + int error; + + signal->started = false; + error = pthread_mutex_init(&signal->mutex, NULL); + if (error != 0) { + return error; + } + + error = pthread_cond_init(&signal->condition, NULL); + if (error != 0) { + pthread_mutex_destroy(&signal->mutex); + } + return error; +} + +static SystemError start_signal_set(StartSignal *signal) +{ + int result = 0; + int error = pthread_mutex_lock(&signal->mutex); + + if (error != 0) { + return error; + } + + if (!signal->started) { + signal->started = true; + result = pthread_cond_signal(&signal->condition); + } + + error = pthread_mutex_unlock(&signal->mutex); + return result != 0 ? result : error; +} + +static SystemError start_signal_wait(StartSignal *signal) +{ + struct timeval now; + struct timespec deadline; + int result = 0; + int error; + + if (gettimeofday(&now, NULL) != 0) { + return errno; + } + + deadline.tv_sec = now.tv_sec + WAIT_TIMEOUT_MS / 1000; + deadline.tv_nsec = + now.tv_usec * 1000 + (WAIT_TIMEOUT_MS % 1000) * 1000000; + if (deadline.tv_nsec >= 1000000000) { + deadline.tv_sec++; + deadline.tv_nsec -= 1000000000; + } + + error = pthread_mutex_lock(&signal->mutex); + if (error != 0) { + return error; + } + + while (!signal->started && result == 0) { + result = pthread_cond_timedwait(&signal->condition, &signal->mutex, + &deadline); + } + + error = pthread_mutex_unlock(&signal->mutex); + return result != 0 ? result : error; +} + +static SystemError start_signal_destroy(StartSignal *signal) +{ + int condition_error = pthread_cond_destroy(&signal->condition); + int mutex_error = pthread_mutex_destroy(&signal->mutex); + + return condition_error != 0 ? condition_error : mutex_error; +} + +static SystemError timeout_error(void) +{ + return ETIMEDOUT; +} + +static void sleep_one_millisecond(void) +{ + usleep(1000); +} +#endif + +static void report_uc_error(const char *operation, uc_err error, + unsigned int iteration) +{ + fprintf(stderr, "%s failed at iteration %u with %u: %s\n", operation, + iteration + 1, (unsigned int)error, uc_strerror(error)); +} + +static void report_system_error(const char *operation, SystemError error, + unsigned int iteration) +{ + fprintf(stderr, "%s failed at iteration %u with %lu\n", operation, + iteration + 1, (unsigned long)error); +} + +static void wait_for_timeout(StopContext *context) +{ + unsigned int elapsed; + + for (elapsed = 0; elapsed < WAIT_TIMEOUT_MS; elapsed++) { + context->query_error = + uc_query(context->uc, UC_QUERY_TIMEOUT, &context->timed_out); + if (context->query_error != UC_ERR_OK || context->timed_out) { + break; + } + sleep_one_millisecond(); + } + if (context->query_error == UC_ERR_OK && !context->timed_out) { + context->wait_error = timeout_error(); + } else if (context->timed_out) { + /* The timer publishes cpu_exit immediately after timed_out. */ + sleep_one_millisecond(); + } +} + +static void stop_after_tb_entry(uc_engine *uc, uint64_t address, uint32_t size, + void *user_data) +{ + StopContext *context = user_data; + + (void)uc; + (void)size; + + if (address != code_address + 4) { + return; + } + + context->calls++; + if (context->source == STOP_FROM_EXTERNAL_THREAD) { + context->signal_error = start_signal_set(&context->entered_signal); + if (context->signal_error == 0) { + context->wait_error = + start_signal_wait(&context->stop_done_signal); + } + } else { + wait_for_timeout(context); + } +} + +static void run_external_emulation(StopContext *context) +{ + context->emu_error = + uc_emu_start(context->uc, code_address, code_address + sizeof(code), + EXTERNAL_RUN_TIMEOUT_US, 0); } #ifdef _WIN32 -static unsigned int __stdcall win32_emu_starter(void* param) +static unsigned int __stdcall emulation_thread(void *opaque) { - uc_err err = emu_starter(param); - _endthreadex(err); - return err; + run_external_emulation(opaque); + return 0; } #else -static void* posix_emu_starter(void* param) +static void *emulation_thread(void *opaque) { - uc_err err = emu_starter(param); - return (void*)err; + run_external_emulation(opaque); + return NULL; } #endif +static bool reset_guest(uc_engine *uc, unsigned int iteration) +{ + uint32_t value = 0; + uc_err error; + + error = uc_reg_write(uc, UC_MIPS_REG_V0, &value); + if (error != UC_ERR_OK) { + report_uc_error("uc_reg_write(V0)", error, iteration); + return false; + } + error = uc_reg_write(uc, UC_MIPS_REG_V1, &value); + if (error != UC_ERR_OK) { + report_uc_error("uc_reg_write(V1)", error, iteration); + return false; + } + return true; +} -int main(int argc, char **argv, char **envp) +static bool verify_stop(StopContext *context, size_t expected_timeout, + uint32_t expected_stop_calls, + unsigned int iteration) { - uc_engine *uc; - uc_err err; - int ret; - uc_hook hhc; - uint32_t val; - EmuStarterParam_t starter_params; + uint32_t pc = 0; + uint32_t v0 = 0; + uint32_t v1 = 0; + size_t timed_out = 0; + uc_err error; + + if (context->emu_error != UC_ERR_OK) { + report_uc_error("uc_emu_start", context->emu_error, iteration); + return false; + } + if (context->query_error != UC_ERR_OK) { + report_uc_error("uc_query in callback", context->query_error, + iteration); + return false; + } + if (context->stop_error != UC_ERR_OK) { + report_uc_error("uc_emu_stop", context->stop_error, iteration); + return false; + } + if (context->signal_error != 0 || context->wait_error != 0) { + report_system_error("callback synchronization", + context->signal_error != 0 ? context->signal_error : + context->wait_error, + iteration); + return false; + } + if (context->calls != 1) { + fprintf(stderr, "callback count %u at iteration %u\n", context->calls, + iteration + 1); + return false; + } + if (context->stop_calls != expected_stop_calls) { + fprintf(stderr, "stop call count %u at iteration %u\n", + context->stop_calls, iteration + 1); + return false; + } + + error = uc_query(context->uc, UC_QUERY_TIMEOUT, &timed_out); + if (error != UC_ERR_OK) { + report_uc_error("uc_query", error, iteration); + return false; + } + error = uc_reg_read(context->uc, UC_MIPS_REG_PC, &pc); + if (error != UC_ERR_OK) { + report_uc_error("uc_reg_read(PC)", error, iteration); + return false; + } + error = uc_reg_read(context->uc, UC_MIPS_REG_V0, &v0); + if (error != UC_ERR_OK) { + report_uc_error("uc_reg_read(V0)", error, iteration); + return false; + } + error = uc_reg_read(context->uc, UC_MIPS_REG_V1, &v1); + if (error != UC_ERR_OK) { + report_uc_error("uc_reg_read(V1)", error, iteration); + return false; + } + + if (timed_out != expected_timeout || pc != code_address + 8 || v0 != 1 || + v1 != 0) { + fprintf(stderr, + "unexpected state at iteration %u: timeout=%zu pc=0x%08" PRIx32 + " v0=%" PRIu32 " v1=%" PRIu32 "\n", + iteration + 1, timed_out, pc, v0, v1); + return false; + } + return true; +} + +static bool run_external_stop_iteration(StopContext *context, + unsigned int iteration) +{ + bool entered_initialized = false; + bool done_initialized = false; + bool thread_started = false; + bool passed = true; + SystemError error; #ifdef _WIN32 - HANDLE th = (HANDLE)-1; + uintptr_t thread_value; + HANDLE thread = NULL; + DWORD wait_result; #else - pthread_t th; + pthread_t thread; #endif - // Initialize emulator in MIPS 32bit little endian mode - printf("uc_open()\n"); - err = uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32, &uc); - if (err) - { - printf("Failed on uc_open() with error returned: %u\n", err); - return err; - } - - // map in a page of mem - printf("uc_mem_map()\n"); - err = uc_mem_map(uc, addr, 0x1000, UC_PROT_ALL); - if (err) - { - printf("Failed on uc_mem_map() with error returned: %u\n", err); - return err; - } - - // write machine code to be emulated to memory - printf("uc_mem_write()\n"); - err = uc_mem_write(uc, addr, loop_test_code, sizeof(loop_test_code)); - if( err ) - { - printf("Failed on uc_mem_write() with error returned: %u\n", err); - return err; - } - - // hook all instructions by having @begin > @end - printf("uc_hook_add()\n"); - uc_hook_add(uc, &hhc, UC_HOOK_CODE, mips_codehook, NULL, 1, 0); - if( err ) - { - printf("Failed on uc_hook_add(code) with error returned: %u\n", err); - return err; - } - - - // start background thread - printf("---- Thread Starting ----\n"); - starter_params.uc = uc; - starter_params.startAddr = addr; - starter_params.endAddr = addr + sizeof(loop_test_code); + memset(&context->entered_signal, 0, sizeof(context->entered_signal)); + memset(&context->stop_done_signal, 0, sizeof(context->stop_done_signal)); + context->source = STOP_FROM_EXTERNAL_THREAD; + context->emu_error = UC_ERR_OK; + context->stop_error = UC_ERR_OK; + context->query_error = UC_ERR_OK; + context->signal_error = 0; + context->wait_error = 0; + context->timed_out = 0; + context->calls = 0; + context->stop_calls = 0; + + if (!reset_guest(context->uc, iteration)) { + return false; + } + error = start_signal_init(&context->entered_signal); + if (error != 0) { + report_system_error("entered signal initialization", error, iteration); + return false; + } + entered_initialized = true; + error = start_signal_init(&context->stop_done_signal); + if (error != 0) { + report_system_error("done signal initialization", error, iteration); + passed = false; + goto cleanup; + } + done_initialized = true; #ifdef _WIN32 - // create thread - th = (HANDLE)_beginthreadex(NULL, 0, win32_emu_starter, &starter_params, CREATE_SUSPENDED, NULL); - if(th == (HANDLE)-1) - { - printf("Failed on _beginthreadex() with error returned: %p\n", _errno()); - return -1; - } - // start thread - ret = ResumeThread(th); - if( ret == -1 ) - { - printf("Failed on ResumeThread() with error returned: %p\n", _errno()); - return -2; - } - // wait 3 seconds - Sleep(3 * 1000); + thread_value = + _beginthreadex(NULL, 0, emulation_thread, context, 0, NULL); + if (thread_value == 0) { + fprintf(stderr, "_beginthreadex failed at iteration %u with %d: %s\n", + iteration + 1, errno, strerror(errno)); + passed = false; + goto cleanup; + } + thread = (HANDLE)thread_value; #else - // add posix code to start the emu_starter() thread - ret = pthread_create(&th, NULL, posix_emu_starter, &starter_params); - if( ret ) - { - printf("Failed on pthread_create() with error returned: %u\n", err); - return -2; - } - // wait 3 seconds - sleep(3); + error = pthread_create(&thread, NULL, emulation_thread, context); + if (error != 0) { + report_system_error("pthread_create", error, iteration); + passed = false; + goto cleanup; + } #endif + thread_started = true; + error = start_signal_wait(&context->entered_signal); + if (error != 0) { + report_system_error("waiting for TB entry", error, iteration); + passed = false; + } + for (context->stop_calls = 0; + context->stop_calls < STOP_COALESCE_CALLS; + context->stop_calls++) { + context->stop_error = uc_emu_stop(context->uc); + if (context->stop_error != UC_ERR_OK) { + report_uc_error("uc_emu_stop", context->stop_error, iteration); + passed = false; + break; + } + } + error = start_signal_set(&context->stop_done_signal); + if (error != 0) { + report_system_error("publishing stop completion", error, iteration); + passed = false; + } - // Stop the thread after it has been let to run in the background for a while - printf("---- Thread Stopping ----\n"); - printf("uc_emu_stop()\n"); - err = uc_emu_stop(uc); - if( err ) - { - printf("Failed on uc_emu_stop() with error returned: %u\n", err); - return err; - } - test_passed_ok = true; - - - // done executing, print some reg values as a test - uc_reg_read(uc, UC_MIPS_REG_PC, &val); printf("pc is %X\n", val); - uc_reg_read(uc, UC_MIPS_REG_A0, &val); printf("a0 is %X\n", val); - - // free resources - printf("uc_close()\n"); - uc_close(uc); - - if( test_passed_ok ) - printf("\n\nTEST PASSED!\n\n"); - else - printf("\n\nTEST FAILED!\n\n"); +#ifdef _WIN32 + wait_result = WaitForSingleObject(thread, WAIT_TIMEOUT_MS * 3); + if (wait_result != WAIT_OBJECT_0) { + report_system_error("waiting for emulation thread", + wait_result == WAIT_TIMEOUT ? ERROR_TIMEOUT : + GetLastError(), + iteration); + return false; + } + if (!CloseHandle(thread)) { + report_system_error("CloseHandle", GetLastError(), iteration); + passed = false; + } + thread = NULL; +#else + error = pthread_join(thread, NULL); + if (error != 0) { + report_system_error("pthread_join", error, iteration); + return false; + } +#endif + thread_started = false; + if (passed && + !verify_stop(context, 0, STOP_COALESCE_CALLS, iteration)) { + passed = false; + } - return 0; +cleanup: + if (thread_started) { + start_signal_set(&context->stop_done_signal); +#ifdef _WIN32 + WaitForSingleObject(thread, WAIT_TIMEOUT_MS * 3); + CloseHandle(thread); +#else + pthread_join(thread, NULL); +#endif + } + if (done_initialized) { + error = start_signal_destroy(&context->stop_done_signal); + if (error != 0) { + report_system_error("done signal cleanup", error, iteration); + passed = false; + } + } + if (entered_initialized) { + error = start_signal_destroy(&context->entered_signal); + if (error != 0) { + report_system_error("entered signal cleanup", error, iteration); + passed = false; + } + } + return passed; } +static bool run_timeout_stop_iteration(StopContext *context, + unsigned int iteration) +{ + context->source = STOP_FROM_TIMEOUT_THREAD; + context->emu_error = UC_ERR_OK; + context->stop_error = UC_ERR_OK; + context->query_error = UC_ERR_OK; + context->signal_error = 0; + context->wait_error = 0; + context->timed_out = 0; + context->calls = 0; + context->stop_calls = 0; + + if (!reset_guest(context->uc, iteration)) { + return false; + } + context->emu_error = + uc_emu_start(context->uc, code_address, code_address + sizeof(code), + TIMER_STOP_TIMEOUT_US, 0); + return verify_stop(context, 1, 0, iteration); +} + +int main(void) +{ + StopContext context = {0}; + uc_engine *uc = NULL; + uc_hook hook; + uc_err error; + unsigned int iteration; + bool passed = true; + + error = uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_LITTLE_ENDIAN, &uc); + if (error != UC_ERR_OK) { + report_uc_error("uc_open", error, 0); + return EXIT_FAILURE; + } + context.uc = uc; + + error = uc_mem_map(uc, code_address, 0x1000, UC_PROT_ALL); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_map", error, 0); + passed = false; + goto cleanup; + } + error = uc_mem_write(uc, code_address, code, sizeof(code)); + if (error != UC_ERR_OK) { + report_uc_error("uc_mem_write", error, 0); + passed = false; + goto cleanup; + } + error = uc_hook_add(uc, &hook, UC_HOOK_CODE, stop_after_tb_entry, &context, + code_address + 4, code_address + 4); + if (error != UC_ERR_OK) { + report_uc_error("uc_hook_add", error, 0); + passed = false; + goto cleanup; + } + + for (iteration = 0; iteration < STOP_STRESS_ITERATIONS; iteration++) { + if (!run_external_stop_iteration(&context, iteration)) { + passed = false; + goto cleanup; + } + } + for (iteration = 0; iteration < STOP_STRESS_ITERATIONS; iteration++) { + if (!run_timeout_stop_iteration(&context, iteration)) { + passed = false; + goto cleanup; + } + } + +cleanup: + error = uc_close(uc); + if (error != UC_ERR_OK) { + report_uc_error("uc_close", error, 0); + passed = false; + } + if (passed) { + printf("exit gate stop stress passed: %u coalesced external, " + "%u timeout\n", + STOP_STRESS_ITERATIONS, STOP_STRESS_ITERATIONS); + } + return passed ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/regress/timeout_segfault.c b/tests/regress/timeout_segfault.c index 43abde0ca1..ac6e9ca33a 100644 --- a/tests/regress/timeout_segfault.c +++ b/tests/regress/timeout_segfault.c @@ -1,147 +1,121 @@ -/* -timeout_segfault.c - -This program shows a case where the emulation timer keeps running after -emulation has ended. It triggers an intermittent segfault when _timeout_fn() -tries to call uc_emu_stop() after emulation has already been cleaned up. This -code is the same as samples/sample_arm.c, except that it adds a timeout on each -call to uc_emu_start(). See issue #78 for more details: -https://github.com/unicorn-engine/unicorn/issues/78 -*/ - #include +#include -// code to be emulated -#define ARM_CODE "\x37\x00\xa0\xe3\x03\x10\x42\xe0" // mov r0, #0x37; sub r1, r2, r3 -#define THUMB_CODE "\x83\xb0" // sub sp, #0xc - -// memory address where emulation starts #define ADDRESS 0x10000 +#define TEST_ITERATIONS 32 +#define TEST_TIMEOUT (UC_SECOND_SCALE * 5) -// number of seconds to wait before timeout -#define TIMEOUT 5 - -static void hook_block(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) +static bool check_error(const char *operation, uc_err error) { - printf(">>> Tracing basic block at 0x%"PRIx64 ", block size = 0x%x\n", address, size); -} - -static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) -{ - printf(">>> Tracing instruction at 0x%"PRIx64 ", instruction size = 0x%x\n", address, size); + if (error == UC_ERR_OK) { + return true; + } + fprintf(stderr, "%s failed with %u: %s\n", operation, (unsigned)error, + uc_strerror(error)); + return false; } -static void test_arm(void) +static bool run_arm(unsigned int iteration) { + const uint8_t code[] = { + 0x37, 0x00, 0xa0, 0xe3, 0x03, 0x10, 0x42, 0xe0, + }; + uint32_t r0 = 0; + uint32_t r1 = 0; + uint32_t r2 = 0x6789; + uint32_t r3 = 0x3333; + size_t timed_out = 0; uc_engine *uc; - uc_err err; - uc_hook trace1, trace2; - - int r0 = 0x1234; // R0 register - int r2 = 0x6789; // R1 register - int r3 = 0x3333; // R2 register - int r1; // R1 register - printf("Emulate ARM code\n"); - - // Initialize emulator in ARM mode - err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc); - if (err) { - printf("Failed on uc_open() with error returned: %u (%s)\n", - err, uc_strerror(err)); - return; + if (!check_error("ARM uc_open", uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc))) { + return false; } - - // map 2MB memory for this emulation - uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - uc_mem_write(uc, ADDRESS, ARM_CODE, sizeof(ARM_CODE) - 1); - - // initialize machine registers - uc_reg_write(uc, UC_ARM_REG_R0, &r0); - uc_reg_write(uc, UC_ARM_REG_R2, &r2); - uc_reg_write(uc, UC_ARM_REG_R3, &r3); - - // tracing all basic blocks with customized callback - uc_hook_add(uc, &trace1, UC_HOOK_BLOCK, hook_block, NULL, 1, 0); - - // tracing one instruction at ADDRESS with customized callback - uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, ADDRESS, ADDRESS); - - // emulate machine code in infinite time (last param = 0), or when - // finishing all the code. - err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(ARM_CODE) -1, UC_SECOND_SCALE * TIMEOUT, 0); - if (err) { - printf("Failed on uc_emu_start() with error returned: %u\n", err); + if (!check_error("ARM uc_mem_map", + uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL)) || + !check_error("ARM uc_mem_write", + uc_mem_write(uc, ADDRESS, code, sizeof(code))) || + !check_error("ARM uc_reg_write(R2)", + uc_reg_write(uc, UC_ARM_REG_R2, &r2)) || + !check_error("ARM uc_reg_write(R3)", + uc_reg_write(uc, UC_ARM_REG_R3, &r3)) || + !check_error("ARM uc_emu_start", + uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), + TEST_TIMEOUT, 0)) || + !check_error("ARM uc_query(UC_QUERY_TIMEOUT)", + uc_query(uc, UC_QUERY_TIMEOUT, &timed_out)) || + !check_error("ARM uc_reg_read(R0)", + uc_reg_read(uc, UC_ARM_REG_R0, &r0)) || + !check_error("ARM uc_reg_read(R1)", + uc_reg_read(uc, UC_ARM_REG_R1, &r1))) { + uc_close(uc); + return false; } - // now print out some registers - printf(">>> Emulation done. Below is the CPU context\n"); - - uc_reg_read(uc, UC_ARM_REG_R0, &r0); - uc_reg_read(uc, UC_ARM_REG_R1, &r1); - printf(">>> R0 = 0x%x\n", r0); - printf(">>> R1 = 0x%x\n", r1); - - uc_close(uc); + if (timed_out != 0) { + fprintf(stderr, "ARM iteration %u timed out\n", iteration + 1); + uc_close(uc); + return false; + } + if (r0 != 0x37 || r1 != r2 - r3) { + fprintf(stderr, + "unexpected ARM result at iteration %u: R0=0x%x R1=0x%x\n", + iteration + 1, r0, r1); + uc_close(uc); + return false; + } + return check_error("ARM uc_close", uc_close(uc)); } -static void test_thumb(void) +static bool run_thumb(unsigned int iteration) { + const uint8_t code[] = {0x83, 0xb0}; + uint32_t sp = 0x1234; + size_t timed_out = 0; uc_engine *uc; - uc_err err; - uc_hook trace1, trace2; - - int sp = 0x1234; // R0 register - printf("Emulate THUMB code\n"); - - // Initialize emulator in ARM mode - err = uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &uc); - if (err) { - printf("Failed on uc_open() with error returned: %u (%s)\n", - err, uc_strerror(err)); - return; + if (!check_error("Thumb uc_open", + uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &uc))) { + return false; } - - // map 2MB memory for this emulation - uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL); - - // write machine code to be emulated to memory - uc_mem_write(uc, ADDRESS, THUMB_CODE, sizeof(THUMB_CODE) - 1); - - // initialize machine registers - uc_reg_write(uc, UC_ARM_REG_SP, &sp); - - // tracing all basic blocks with customized callback - uc_hook_add(uc, &trace1, UC_HOOK_BLOCK, hook_block, NULL, 1, 0); - - // tracing one instruction at ADDRESS with customized callback - uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, ADDRESS, ADDRESS); - - // emulate machine code in infinite time (last param = 0), or when - // finishing all the code. - err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(THUMB_CODE) -1, UC_SECOND_SCALE * TIMEOUT, 0); - if (err) { - printf("Failed on uc_emu_start() with error returned: %u\n", err); + if (!check_error("Thumb uc_mem_map", + uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL)) || + !check_error("Thumb uc_mem_write", + uc_mem_write(uc, ADDRESS, code, sizeof(code))) || + !check_error("Thumb uc_reg_write(SP)", + uc_reg_write(uc, UC_ARM_REG_SP, &sp)) || + !check_error("Thumb uc_emu_start", + uc_emu_start(uc, ADDRESS | 1, 0, TEST_TIMEOUT, 1)) || + !check_error("Thumb uc_query(UC_QUERY_TIMEOUT)", + uc_query(uc, UC_QUERY_TIMEOUT, &timed_out)) || + !check_error("Thumb uc_reg_read(SP)", + uc_reg_read(uc, UC_ARM_REG_SP, &sp))) { + uc_close(uc); + return false; } - // now print out some registers - printf(">>> Emulation done. Below is the CPU context\n"); - - uc_reg_read(uc, UC_ARM_REG_SP, &sp); - printf(">>> SP = 0x%x\n", sp); - - uc_close(uc); + if (timed_out != 0) { + fprintf(stderr, "Thumb iteration %u timed out\n", iteration + 1); + uc_close(uc); + return false; + } + if (sp != 0x1228) { + fprintf(stderr, "unexpected Thumb result at iteration %u: SP=0x%x\n", + iteration + 1, sp); + uc_close(uc); + return false; + } + return check_error("Thumb uc_close", uc_close(uc)); } -int main(int argc, char **argv, char **envp) +int main(void) { - test_arm(); - printf("==========================\n"); - test_thumb(); + unsigned int i; + for (i = 0; i < TEST_ITERATIONS; i++) { + if (!run_arm(i) || !run_thumb(i)) { + return 1; + } + } return 0; } diff --git a/tests/regress/translator_buffer.py b/tests/regress/translator_buffer.py index 6b323681c2..452629880b 100755 --- a/tests/regress/translator_buffer.py +++ b/tests/regress/translator_buffer.py @@ -32,9 +32,18 @@ def __page_aligned(address): self.set_hooks() def set_hooks(self): - self.mu.hook_add(UC_HOOK_MEM_WRITE, self.hook_mem_access) - self.mu.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED, self.hook_mem_invalid) - self.mu.hook_add(UC_HOOK_MEM_FETCH_UNMAPPED, self.hook_mem_fetch_unmapped) + self.hooks = ( + self.mu.hook_add(UC_HOOK_MEM_WRITE, self.hook_mem_access), + self.mu.hook_add(UC_HOOK_MEM_READ_UNMAPPED | + UC_HOOK_MEM_WRITE_UNMAPPED, + self.hook_mem_invalid), + self.mu.hook_add(UC_HOOK_MEM_FETCH_UNMAPPED, + self.hook_mem_fetch_unmapped), + ) + + def close(self): + for hook in self.hooks: + self.mu.hook_del(hook) def hook_mem_fetch_unmapped(self, uc, access, address, size, value, user_data): next_ip = self.unicorn_code + size @@ -71,11 +80,13 @@ def write_reg(self, reg, value): class TranslatorBuffer(regress.RegressTest): def init_unicorn(self, ip, sp, magic): emu = Emulator(ip, sp) + try: + emu.write_data(ip, b"\xf4" * 8) + emu.write_data(sp, struct.pack(" -#define BINARY "\x90" -#define MEMORY_SIZE 4 * 1024 -#define STARTING_ADDRESS 100 * 1024 +#define ADDRESS (100 * 1024) -int main(int argc, char **argv, char **envp) { - uc_engine *uc; - if (uc_open(UC_ARCH_X86, UC_MODE_16, &uc)) { - printf("uc_open(…) failed\n"); - return 1; - } - uc_mem_map(uc, STARTING_ADDRESS, MEMORY_SIZE, UC_PROT_ALL); - if (uc_mem_write(uc, STARTING_ADDRESS, BINARY, sizeof(BINARY) - 1)) { - printf("uc_mem_write(…) failed\n"); - return 1; - } - printf("uc_emu_start(…)\n"); - uc_emu_start(uc, STARTING_ADDRESS, STARTING_ADDRESS + sizeof(BINARY) - 1, 0, 20); - printf("done\n"); - return 0; +int main(void) +{ + const uint8_t code[] = {0x90}; + uint32_t eip = 0; + uc_engine *uc; + uc_err err; + + if (uc_open(UC_ARCH_X86, UC_MODE_16, &uc) != UC_ERR_OK) { + return 1; + } + if (uc_mem_map(uc, ADDRESS, 0x1000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, ADDRESS, code, sizeof(code)) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + + err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 1); + if (err != UC_ERR_FETCH_UNMAPPED || + uc_reg_read(uc, UC_X86_REG_EIP, &eip) != UC_ERR_OK) { + uc_close(uc); + return 1; + } + if (uc_close(uc) != UC_ERR_OK) { + return 1; + } + return eip == (ADDRESS & UINT16_MAX) ? 0 : 1; } diff --git a/tests/regress/x86_vex.c b/tests/regress/x86_vex.c index 1808de4243..d79f8a1228 100644 --- a/tests/regress/x86_vex.c +++ b/tests/regress/x86_vex.c @@ -1,83 +1,78 @@ -#include "unicorn/unicorn.h" -#include -#include +#include +#include -#define OK(x) {uc_err __err; if ((__err = x)) { fprintf(stderr, "%s", uc_strerror(__err)); assert(false); } } -static void test_vmovdqu(void) +#define CODE_ADDRESS 0x1000 +#define DATA_ADDRESS 0x2000 + +static bool test_vmovdqu_xmm(void) { + const uint8_t code[] = {0xc5, 0xfa, 0x6f, 0x07}; + const uint8_t expected[16] = { + 0xad, 0xfa, 0x5c, 0x6d, 0x45, 0x4a, 0x93, 0x40, + 0xd2, 0x00, 0xde, 0x02, 0x89, 0xe8, 0x94, 0x40, + }; + uint8_t xmm0[16] = {0}; + uint32_t edi = DATA_ADDRESS; uc_engine *uc; - int r_esi = 0x1234; - int r_edi = 0x7890; - - uint64_t r_xmm0[2] = {0x08090a0b0c0d0e0f, 0x0001020304050607}; - - /* 128 bit at address esi (0x1234) this should not be read into xmm0 */ - char mem_esi[] = { '\xE7', '\x1D', '\xA7', '\xE8', '\x88', '\xE4', '\x94', '\x40', '\x54', '\x74', '\x24', '\x97', '\x1F', '\x2E', '\xB6', '\x40' }; - - /* 128 bit at address edi (0x7890) this SHOULD be read into xmm0 */ - char mem_edi[] = { '\xAD', '\xFA', '\x5C', '\x6D', '\x45', '\x4A', '\x93', '\x40', '\xD2', '\x00', '\xDE', '\x02', '\x89', '\xE8', '\x94', '\x40' }; - - /* vmovdqu xmm0, [edi] */ - char code[] = { '\xC5', '\xFA', '\x6F', '\x07' }; - - /* initialize memory and run emulation */ - OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); - OK(uc_mem_map(uc, 0, 2 * 1024 * 1024, UC_PROT_ALL)); - - OK(uc_mem_write(uc, 0, code, sizeof(code) / sizeof(code[0]))); - - // initialize machine registers; - OK(uc_reg_write(uc, UC_X86_REG_XMM0, &r_xmm0)); - - OK(uc_reg_write(uc, UC_X86_REG_ESI, &r_esi)); - OK(uc_reg_write(uc, UC_X86_REG_EDI, &r_edi)); - OK(uc_mem_write(uc, r_esi, mem_esi, sizeof(mem_esi) / sizeof(mem_esi[0]))); - OK(uc_mem_write(uc, r_edi, mem_edi, sizeof(mem_edi) / sizeof(mem_edi[0]))); - - OK(uc_emu_start(uc, 0, sizeof(code) / sizeof(code[0]), 0, 0)); - - /* Read xmm0 after emulation */ - OK(uc_reg_read(uc, UC_X86_REG_XMM0, &r_xmm0)); - - - assert(0x4094e88902de00d2 == r_xmm0[0] && 0x40934a456d5cfaad == r_xmm0[1]); + if (uc_open(UC_ARCH_X86, UC_MODE_32, &uc) != UC_ERR_OK) { + return false; + } + if (uc_ctl_set_cpu_model(uc, UC_CPU_X86_HASWELL) != UC_ERR_OK || + uc_mem_map(uc, CODE_ADDRESS, 0x2000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, CODE_ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_mem_write(uc, DATA_ADDRESS, expected, sizeof(expected)) != + UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_EDI, &edi) != UC_ERR_OK || + uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + sizeof(code), 0, 1) != + UC_ERR_OK || + uc_reg_read(uc, UC_X86_REG_XMM0, xmm0) != UC_ERR_OK) { + uc_close(uc); + return false; + } - OK(uc_close(uc)); + if (uc_close(uc) != UC_ERR_OK) { + return false; + } + return memcmp(xmm0, expected, sizeof(expected)) == 0; } -/* https://github.com/unicorn-engine/unicorn/issues/1656 */ -static void test_vex_l(void) +static bool test_vmovdqu_ymm(void) { + const uint8_t code[] = {0xc5, 0xfe, 0x6f, 0x09}; + const uint8_t expected[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + }; + uint8_t ymm1[32] = {0}; + uint64_t rcx = DATA_ADDRESS; uc_engine *uc; - uc_err err; - - /* vmovdqu ymm1, [rcx] */ - char code[] = { '\xC5', '\xFE', '\x6F', '\x09' }; - - /* initialize memory and run emulation */ - OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); - OK(uc_mem_map(uc, 0, 2 * 1024 * 1024, UC_PROT_ALL)); - OK(uc_mem_write(uc, 0, code, sizeof(code) / sizeof(code[0]))); - - err = uc_emu_start(uc, 0, sizeof(code) / sizeof(code[0]), 0, 0); - if(err != UC_ERR_INSN_INVALID) { - fprintf(stderr, "%s", uc_strerror(err)); - assert(false); + if (uc_open(UC_ARCH_X86, UC_MODE_64, &uc) != UC_ERR_OK) { + return false; + } + if (uc_ctl_set_cpu_model(uc, UC_CPU_X86_HASWELL) != UC_ERR_OK || + uc_mem_map(uc, CODE_ADDRESS, 0x2000, UC_PROT_ALL) != UC_ERR_OK || + uc_mem_write(uc, CODE_ADDRESS, code, sizeof(code)) != UC_ERR_OK || + uc_mem_write(uc, DATA_ADDRESS, expected, sizeof(expected)) != + UC_ERR_OK || + uc_reg_write(uc, UC_X86_REG_RCX, &rcx) != UC_ERR_OK || + uc_emu_start(uc, CODE_ADDRESS, CODE_ADDRESS + sizeof(code), 0, 1) != + UC_ERR_OK || + uc_reg_read(uc, UC_X86_REG_YMM1, ymm1) != UC_ERR_OK) { + uc_close(uc); + return false; } - OK(uc_close(uc)); + if (uc_close(uc) != UC_ERR_OK) { + return false; + } + return memcmp(ymm1, expected, sizeof(expected)) == 0; } - -/* TODO: Add more vex prefixed instructions - Suggestions: vxorpd, vxorps, vandpd, ... */ -int main(int argc, char **argv, char **envp) +int main(void) { - test_vmovdqu(); - test_vex_l(); - return 0; + return test_vmovdqu_xmm() && test_vmovdqu_ymm() ? 0 : 1; } - diff --git a/tests/unit/test_arm.c b/tests/unit/test_arm.c index e35e8c87c2..933da20706 100644 --- a/tests/unit/test_arm.c +++ b/tests/unit/test_arm.c @@ -1,4 +1,5 @@ #include "unicorn_test.h" +#include #include const uint64_t code_start = 0x1000; @@ -25,6 +26,14 @@ static void test_arm_emit32(uint8_t *code, int offset, uint32_t insn) code[offset + 3] = (uint8_t)(insn >> 24); } +static void test_arm_emit_thumb32(uint8_t *code, int offset, uint32_t insn) +{ + code[offset] = (uint8_t)(insn >> 16); + code[offset + 1] = (uint8_t)(insn >> 24); + code[offset + 2] = (uint8_t)insn; + code[offset + 3] = (uint8_t)(insn >> 8); +} + static void test_arm_enable_vfp(uc_engine *uc) { uint32_t cpacr = 0xfU << 20; @@ -34,6 +43,17 @@ static void test_arm_enable_vfp(uc_engine *uc) OK(uc_reg_write(uc, UC_ARM_REG_FPEXC, &fpexc)); } +static void test_arm_query_initial_thumb_mode(void) +{ + uc_engine *uc; + size_t mode; + + OK(uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &uc)); + OK(uc_query(uc, UC_QUERY_MODE, &mode)); + TEST_CHECK(mode == UC_MODE_THUMB); + OK(uc_close(uc)); +} + static uint32_t test_arm_id_isar6_read(uc_engine *uc) { uc_arm_cp_reg reg = { @@ -51,6 +71,24 @@ static uint32_t test_arm_id_isar6_read(uc_engine *uc) return (uint32_t)reg.val; } +static uint32_t test_arm_cp14_read(uc_engine *uc, uint32_t crm, + uint32_t opc2) +{ + uc_arm_cp_reg reg = { + .cp = 14, + .is64 = 0, + .sec = 0, + .crn = 0, + .crm = crm, + .opc1 = 0, + .opc2 = opc2, + .val = 0, + }; + + OK(uc_reg_read(uc, UC_ARM_REG_CP_REG, ®)); + return (uint32_t)reg.val; +} + static void test_arm_i8mm_q_run(uint32_t insn, const uint8_t *initial, const uint8_t *n, const uint8_t *m, const uint8_t *expected) @@ -347,6 +385,252 @@ static void test_arm_nop(void) OK(uc_close(uc)); } +static void test_arm_legacy_count_transition(void) +{ + const char code[] = + "\x01\x00\xa0\xe3" /* mov r0, #1 */ + "\x02\x10\xa0\xe3"; /* mov r1, #2 */ + uint32_t r0; + uint32_t r1; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_ARM, UC_MODE_ARM, code, + sizeof(code) - 1, UC_CPU_ARM_CORTEX_A15); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, + 0, 1)); + OK(uc_reg_read(uc, UC_ARM_REG_R0, &r0)); + OK(uc_reg_read(uc, UC_ARM_REG_R1, &r1)); + TEST_CHECK(r0 == 1); + TEST_CHECK(r1 == 0); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, + 0, 0)); + OK(uc_reg_read(uc, UC_ARM_REG_R1, &r1)); + TEST_CHECK(r1 == 2); + + OK(uc_close(uc)); +} + +typedef struct ArmExclusiveCase { + const char *name; + uint32_t size; + uint32_t insns[6]; +} ArmExclusiveCase; + +static void test_arm_exclusive_single_run(uc_mode mode, + const ArmExclusiveCase *test_case) +{ + const uint64_t data_address = code_start + 0x1000; + const uint8_t initial[8] = { + 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, + }; + const uint8_t stored[4] = {0xd4, 0xc3, 0xb2, 0xa1}; + uint8_t expected[8]; + uint8_t memory[8] = {0}; + uint8_t code[sizeof(test_case->insns)]; + uint32_t r0 = (uint32_t)data_address; + uint32_t r1 = 0xffffffff; + uint32_t r2 = 0xffffffff; + uint32_t r3 = 0xa1b2c3d4; + uint32_t r4 = 0xffffffff; + uint32_t r5 = 0x13579bdf; + uint32_t r6 = 0xffffffff; + uint32_t r7 = 0xffffffff; + uint32_t r8 = 0x2468ace0; + uint32_t expected_load; + uint32_t expected_store; + uc_engine *uc; + size_t i; + + for (i = 0; i < sizeof(test_case->insns) / + sizeof(test_case->insns[0]); i++) { + if ((mode & UC_MODE_THUMB) != 0) { + test_arm_emit_thumb32(code, (int)i * 4, test_case->insns[i]); + } else { + test_arm_emit32(code, (int)i * 4, test_case->insns[i]); + } + } + + uc_common_setup(&uc, UC_ARCH_ARM, mode, (const char *)code, + sizeof(code), UC_CPU_ARM_CORTEX_A15); + OK(uc_mem_write(uc, data_address, initial, sizeof(initial))); + OK(uc_reg_write(uc, UC_ARM_REG_R0, &r0)); + OK(uc_reg_write(uc, UC_ARM_REG_R1, &r1)); + OK(uc_reg_write(uc, UC_ARM_REG_R2, &r2)); + OK(uc_reg_write(uc, UC_ARM_REG_R3, &r3)); + OK(uc_reg_write(uc, UC_ARM_REG_R4, &r4)); + OK(uc_reg_write(uc, UC_ARM_REG_R5, &r5)); + OK(uc_reg_write(uc, UC_ARM_REG_R6, &r6)); + OK(uc_reg_write(uc, UC_ARM_REG_R7, &r7)); + OK(uc_reg_write(uc, UC_ARM_REG_R8, &r8)); + + OK(uc_emu_start(uc, code_start | ((mode & UC_MODE_THUMB) != 0), + code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_ARM_REG_R1, &r1)); + OK(uc_reg_read(uc, UC_ARM_REG_R2, &r2)); + OK(uc_reg_read(uc, UC_ARM_REG_R3, &r3)); + OK(uc_reg_read(uc, UC_ARM_REG_R4, &r4)); + OK(uc_reg_read(uc, UC_ARM_REG_R5, &r5)); + OK(uc_reg_read(uc, UC_ARM_REG_R6, &r6)); + OK(uc_reg_read(uc, UC_ARM_REG_R7, &r7)); + OK(uc_reg_read(uc, UC_ARM_REG_R8, &r8)); + OK(uc_mem_read(uc, data_address, memory, sizeof(memory))); + + expected_load = test_case->size == 1 ? 0xef : + test_case->size == 2 ? 0xcdef : 0x89abcdef; + expected_store = test_case->size == 1 ? 0xd4 : + test_case->size == 2 ? 0xc3d4 : 0xa1b2c3d4; + memcpy(expected, initial, sizeof(expected)); + memcpy(expected, stored, test_case->size); + TEST_CHECK_(r1 == expected_load, "%s first load=0x%08x", + test_case->name, r1); + TEST_CHECK_(r2 == 0, "%s first status=%u", test_case->name, r2); + TEST_CHECK_(r3 == 0xa1b2c3d4, "%s first source=0x%08x", + test_case->name, r3); + TEST_CHECK_(r4 == 1, "%s repeated status=%u", test_case->name, r4); + TEST_CHECK_(r5 == 0x13579bdf, "%s repeated source=0x%08x", + test_case->name, r5); + TEST_CHECK_(r6 == expected_store, + "%s second load=0x%08x", test_case->name, r6); + TEST_CHECK_(r7 == 1, "%s cleared status=%u", test_case->name, r7); + TEST_CHECK_(r8 == 0x2468ace0, "%s cleared source=0x%08x", + test_case->name, r8); + TEST_CHECK_(memcmp(memory, expected, sizeof(expected)) == 0, + "%s memory mismatch", test_case->name); + + OK(uc_close(uc)); +} + +static void test_arm_exclusive_double_run(uc_mode mode, + const ArmExclusiveCase *test_case) +{ + const uint64_t data_address = code_start + 0x1000; + const uint8_t initial[8] = { + 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, + }; + const uint8_t expected[8] = { + 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, + }; + uint8_t memory[8] = {0}; + uint8_t code[sizeof(test_case->insns)]; + uint32_t regs[13] = {0}; + uc_engine *uc; + int reg_ids[13]; + size_t i; + + regs[4] = 0x55667788; + regs[5] = 0x11223344; + regs[6] = 0xffffffff; + regs[7] = 0xffffffff; + regs[8] = 0x13579bdf; + regs[9] = 0x2468ace0; + regs[10] = 0xffffffff; + regs[11] = 0xffffffff; + regs[12] = 0xffffffff; + for (i = 0; i < sizeof(reg_ids) / sizeof(reg_ids[0]); i++) { + reg_ids[i] = UC_ARM_REG_R0 + (int)i; + } + for (i = 0; i < sizeof(test_case->insns) / + sizeof(test_case->insns[0]); i++) { + if ((mode & UC_MODE_THUMB) != 0) { + test_arm_emit_thumb32(code, (int)i * 4, test_case->insns[i]); + } else { + test_arm_emit32(code, (int)i * 4, test_case->insns[i]); + } + } + + uc_common_setup(&uc, UC_ARCH_ARM, mode, (const char *)code, + sizeof(code), UC_CPU_ARM_CORTEX_A15); + OK(uc_mem_write(uc, data_address, initial, sizeof(initial))); + regs[0] = (uint32_t)data_address; + for (i = 0; i < sizeof(reg_ids) / sizeof(reg_ids[0]); i++) { + OK(uc_reg_write(uc, reg_ids[i], ®s[i])); + } + + OK(uc_emu_start(uc, code_start | ((mode & UC_MODE_THUMB) != 0), + code_start + sizeof(code), 0, 0)); + for (i = 0; i < sizeof(reg_ids) / sizeof(reg_ids[0]); i++) { + OK(uc_reg_read(uc, reg_ids[i], ®s[i])); + } + OK(uc_mem_read(uc, data_address, memory, sizeof(memory))); + + TEST_CHECK_(regs[2] == 0x89abcdef, "%s first low=0x%08x", + test_case->name, regs[2]); + TEST_CHECK_(regs[3] == 0x01234567, "%s first high=0x%08x", + test_case->name, regs[3]); + TEST_CHECK_(regs[4] == 0x55667788, "%s store low=0x%08x", + test_case->name, regs[4]); + TEST_CHECK_(regs[5] == 0x11223344, "%s store high=0x%08x", + test_case->name, regs[5]); + TEST_CHECK_(regs[6] == 0, "%s first status=%u", + test_case->name, regs[6]); + TEST_CHECK_(regs[7] == 1, "%s repeated status=%u", + test_case->name, regs[7]); + TEST_CHECK_(regs[8] == 0x13579bdf, "%s repeated low=0x%08x", + test_case->name, regs[8]); + TEST_CHECK_(regs[9] == 0x2468ace0, "%s repeated high=0x%08x", + test_case->name, regs[9]); + TEST_CHECK_(regs[10] == 0x55667788, "%s second low=0x%08x", + test_case->name, regs[10]); + TEST_CHECK_(regs[11] == 0x11223344, "%s second high=0x%08x", + test_case->name, regs[11]); + TEST_CHECK_(regs[12] == 1, "%s cleared status=%u", + test_case->name, regs[12]); + TEST_CHECK_(memcmp(memory, expected, sizeof(expected)) == 0, + "%s memory mismatch", test_case->name); + + OK(uc_close(uc)); +} + +static void test_arm_exclusive_cases(uc_mode mode, + const ArmExclusiveCase *test_cases, + size_t count) +{ + size_t i; + + for (i = 0; i < count; i++) { + if (test_cases[i].size == 8) { + test_arm_exclusive_double_run(mode, &test_cases[i]); + } else { + test_arm_exclusive_single_run(mode, &test_cases[i]); + } + } +} + +static void test_arm_exclusive_monitor(void) +{ + static const ArmExclusiveCase test_cases[] = { + {"A32 word", 4, {0xe1901f9f, 0xe1802f93, 0xe1804f95, + 0xe1906f9f, 0xf57ff01f, 0xe1807f98} }, + {"A32 byte", 1, {0xe1d01f9f, 0xe1c02f93, 0xe1c04f95, + 0xe1d06f9f, 0xf57ff01f, 0xe1c07f98} }, + {"A32 half", 2, {0xe1f01f9f, 0xe1e02f93, 0xe1e04f95, + 0xe1f06f9f, 0xf57ff01f, 0xe1e07f98} }, + {"A32 double", 8, {0xe1b02f9f, 0xe1a06f94, 0xe1a07f98, + 0xe1b0af9f, 0xf57ff01f, 0xe1a0cf98} }, + }; + + test_arm_exclusive_cases(UC_MODE_ARM, test_cases, + sizeof(test_cases) / sizeof(test_cases[0])); +} + +static void test_arm_thumb2_exclusive_monitor(void) +{ + static const ArmExclusiveCase test_cases[] = { + {"Thumb-2 word", 4, {0xe8501f00, 0xe8403200, 0xe8405400, + 0xe8506f00, 0xf3bf8f2f, 0xe8408700} }, + {"Thumb-2 byte", 1, {0xe8d01f4f, 0xe8c03f42, 0xe8c05f44, + 0xe8d06f4f, 0xf3bf8f2f, 0xe8c08f47} }, + {"Thumb-2 half", 2, {0xe8d01f5f, 0xe8c03f52, 0xe8c05f54, + 0xe8d06f5f, 0xf3bf8f2f, 0xe8c08f57} }, + {"Thumb-2 double", 8, {0xe8d0237f, 0xe8c04576, 0xe8c08977, + 0xe8d0ab7f, 0xf3bf8f2f, 0xe8c0897c} }, + }; + + test_arm_exclusive_cases(UC_MODE_THUMB, test_cases, + sizeof(test_cases) / sizeof(test_cases[0])); +} + static void test_arm_thumb_sub(void) { uc_engine *uc; @@ -513,6 +797,324 @@ static void test_arm_thumb_ite(void) OK(uc_close(uc)); } +typedef struct ArmItStop { + uc_err error; + uint32_t calls; +} ArmItStop; + +static void test_arm_thumb_it_stop_callback(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + ArmItStop *stop = (ArmItStop *)user_data; + + stop->calls++; + stop->error = uc_emu_stop(uc); +} + +static void test_arm_thumb_it_pending_stop(void) +{ + const uint8_t code[] = { + 0x9a, 0x42, /* cmp r2,r3 */ + 0x15, 0xbf, /* itete ne */ + 0x00, 0x9a, /* ldrne r2,[sp] */ + 0x01, 0x9a, /* ldreq r2,[sp,#4] */ + 0x78, 0x23, /* movne r3,#0x78 */ + 0x15, 0x23, /* moveq r3,#0x15 */ + }; + const uint32_t stack_address = 0x8000; + ArmItStop stop = {0}; + uint32_t r2 = 0; + uint32_t r3 = 1; + uint32_t pc; + uint32_t value; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_ARM, UC_MODE_THUMB, (const char *)code, + sizeof(code), UC_CPU_ARM_CORTEX_A15); + OK(uc_mem_map(uc, stack_address, 0x1000, UC_PROT_ALL)); + value = LEINT32(0x68); + OK(uc_mem_write(uc, stack_address, &value, sizeof(value))); + value = LEINT32(0x4d); + OK(uc_mem_write(uc, stack_address + 4, &value, sizeof(value))); + OK(uc_reg_write(uc, UC_ARM_REG_SP, &stack_address)); + OK(uc_reg_write(uc, UC_ARM_REG_R2, &r2)); + OK(uc_reg_write(uc, UC_ARM_REG_R3, &r3)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, + test_arm_thumb_it_stop_callback, &stop, + code_start + 4, code_start + 4)); + + OK(uc_emu_start(uc, code_start | 1, code_start + sizeof(code), 0, 0)); + OK(stop.error); + OK(uc_reg_read(uc, UC_ARM_REG_R2, &r2)); + OK(uc_reg_read(uc, UC_ARM_REG_R3, &r3)); + OK(uc_reg_read(uc, UC_ARM_REG_PC, &pc)); + TEST_CHECK(stop.calls == 1); + TEST_CHECK(r2 == 0x68); + TEST_CHECK(r3 == 0x78); + TEST_CHECK_(pc == code_start + sizeof(code), "pc=0x%x", pc); + + OK(uc_close(uc)); +} + +typedef struct ArmFetchPredicateTrace { + uint64_t fetch_address; + uint32_t fetch_pc; + uint32_t fetch_size; + uint32_t fetch_r0; + uint32_t fetch_r1; + uint64_t code_address[2]; + uint64_t event_address[4]; + uint32_t event_pc[4]; + uint32_t event_size[4]; + uint8_t event_type[4]; + size_t fetch_count; + size_t code_count; + size_t event_count; + bool mutate_z; + bool code_sets_z; +} ArmFetchPredicateTrace; + +enum { + ARM_FETCH_EVENT_FETCH, + ARM_FETCH_EVENT_CODE, +}; + +static void test_arm_fetch_predicate_callback(uc_engine *uc, + uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + ArmFetchPredicateTrace *trace = (ArmFetchPredicateTrace *)user_data; + size_t event_index = trace->event_count; + + TEST_CHECK(type == UC_MEM_FETCH); + TEST_CHECK(value == 0); + if (TEST_CHECK(event_index < 4)) { + trace->event_type[event_index] = ARM_FETCH_EVENT_FETCH; + trace->event_address[event_index] = address; + trace->event_size[event_index] = size; + } + trace->event_count++; + trace->fetch_count++; + trace->fetch_address = address; + trace->fetch_size = size; + OK(uc_reg_read(uc, UC_ARM_REG_PC, &trace->fetch_pc)); + OK(uc_reg_read(uc, UC_ARM_REG_R0, &trace->fetch_r0)); + OK(uc_reg_read(uc, UC_ARM_REG_R1, &trace->fetch_r1)); + if (event_index < 4) { + trace->event_pc[event_index] = trace->fetch_pc; + } +} + +static void test_arm_fetch_predicate_code_callback(uc_engine *uc, + uint64_t address, + uint32_t size, + void *user_data) +{ + ArmFetchPredicateTrace *trace = (ArmFetchPredicateTrace *)user_data; + uint32_t cpsr; + uint32_t pc; + + if (TEST_CHECK(trace->code_count < 2)) { + trace->code_address[trace->code_count] = address; + } + trace->code_count++; + if (TEST_CHECK(trace->event_count < 4)) { + trace->event_type[trace->event_count] = ARM_FETCH_EVENT_CODE; + trace->event_address[trace->event_count] = address; + trace->event_size[trace->event_count] = size; + OK(uc_reg_read(uc, UC_ARM_REG_PC, &pc)); + trace->event_pc[trace->event_count] = pc; + } + trace->event_count++; + + if (trace->mutate_z) { + OK(uc_reg_read(uc, UC_ARM_REG_CPSR, &cpsr)); + if (trace->code_sets_z) { + cpsr |= 1U << 30; + } else { + cpsr &= ~(1U << 30); + } + OK(uc_reg_write(uc, UC_ARM_REG_CPSR, &cpsr)); + } +} + +static void test_arm_fetch_assert_event(const ArmFetchPredicateTrace *trace, + size_t index, uint8_t type, + uint64_t address, uint32_t size) +{ + TEST_ASSERT(index < trace->event_count); + TEST_CHECK(trace->event_type[index] == type); + TEST_CHECK(trace->event_address[index] == address); + TEST_CHECK(trace->event_pc[index] == address); + TEST_CHECK(trace->event_size[index] == size); +} + +static void test_arm_fetch_predicate(void) +{ + const uint8_t code[] = { + 0x01, 0x00, 0xa0, 0x03, /* moveq r0,#1 */ + 0x02, 0x10, 0xa0, 0x13, /* movne r1,#2 */ + }; + ArmFetchPredicateTrace trace = {0}; + uint32_t cpsr; + uint32_t r0 = 0; + uint32_t r1 = 0; + uc_engine *uc; + uc_hook code_hook; + uc_hook fetch_hook; + + uc_common_setup(&uc, UC_ARCH_ARM, UC_MODE_ARM, (const char *)code, + sizeof(code), UC_CPU_ARM_CORTEX_A15); + OK(uc_reg_read(uc, UC_ARM_REG_CPSR, &cpsr)); + cpsr &= ~(1U << 30); + OK(uc_reg_write(uc, UC_ARM_REG_CPSR, &cpsr)); + OK(uc_hook_add(uc, &fetch_hook, UC_HOOK_MEM_FETCH, + test_arm_fetch_predicate_callback, &trace, 1, 0)); + OK(uc_hook_add(uc, &code_hook, UC_HOOK_CODE, + test_arm_fetch_predicate_code_callback, &trace, 1, 0)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 2)); + + TEST_CHECK(trace.code_count == 2); + TEST_CHECK(trace.code_address[0] == code_start); + TEST_CHECK(trace.code_address[1] == code_start + 4); + TEST_CHECK(trace.fetch_count == 1); + TEST_CHECK(trace.fetch_address == code_start + 4); + TEST_CHECK(trace.fetch_pc == code_start + 4); + TEST_CHECK(trace.fetch_size == 4); + TEST_CHECK(trace.fetch_r0 == 0); + TEST_CHECK(trace.fetch_r1 == 0); + TEST_CHECK(trace.event_count == 3); + test_arm_fetch_assert_event(&trace, 0, ARM_FETCH_EVENT_CODE, code_start, + 4); + test_arm_fetch_assert_event(&trace, 1, ARM_FETCH_EVENT_CODE, + code_start + 4, 4); + test_arm_fetch_assert_event(&trace, 2, ARM_FETCH_EVENT_FETCH, + code_start + 4, 4); + OK(uc_reg_read(uc, UC_ARM_REG_R0, &r0)); + OK(uc_reg_read(uc, UC_ARM_REG_R1, &r1)); + TEST_CHECK(r0 == 0); + TEST_CHECK(r1 == 2); + + OK(uc_hook_del(uc, code_hook)); + OK(uc_hook_del(uc, fetch_hook)); + OK(uc_close(uc)); +} + +static void test_arm_thumb_fetch_predicate(void) +{ + const uint8_t code[] = { + 0x80, 0x42, /* cmp r0,r0 */ + 0x0c, 0xbf, /* ite eq */ + 0x01, 0x21, /* moveq r1,#1 */ + 0x02, 0x22, /* movne r2,#2 */ + }; + ArmFetchPredicateTrace trace = {0}; + uint32_t r1 = 0; + uint32_t r2 = 0; + uc_engine *uc; + uc_hook code_hook; + uc_hook fetch_hook; + + uc_common_setup(&uc, UC_ARCH_ARM, UC_MODE_THUMB, (const char *)code, + sizeof(code), UC_CPU_ARM_CORTEX_A15); + OK(uc_hook_add(uc, &fetch_hook, UC_HOOK_MEM_FETCH, + test_arm_fetch_predicate_callback, &trace, + code_start + 4, code_start + 6)); + OK(uc_hook_add(uc, &code_hook, UC_HOOK_CODE, + test_arm_fetch_predicate_code_callback, &trace, + code_start + 4, code_start + 6)); + + OK(uc_emu_start(uc, code_start | 1, code_start + sizeof(code), 0, 0)); + + TEST_CHECK(trace.fetch_count == 1); + TEST_CHECK(trace.fetch_address == code_start + 4); + TEST_CHECK(trace.fetch_pc == code_start + 4); + TEST_CHECK(trace.fetch_size == 2); + TEST_CHECK(trace.fetch_r0 == 0); + TEST_CHECK(trace.fetch_r1 == 0); + TEST_CHECK(trace.code_count == 1); + TEST_CHECK(trace.code_address[0] == code_start + 4); + TEST_CHECK(trace.event_count == 2); + test_arm_fetch_assert_event(&trace, 0, ARM_FETCH_EVENT_FETCH, + code_start + 4, 2); + test_arm_fetch_assert_event(&trace, 1, ARM_FETCH_EVENT_CODE, + code_start + 4, 2); + OK(uc_reg_read(uc, UC_ARM_REG_R1, &r1)); + OK(uc_reg_read(uc, UC_ARM_REG_R2, &r2)); + TEST_CHECK(r1 == 1); + TEST_CHECK(r2 == 0); + + OK(uc_hook_del(uc, code_hook)); + OK(uc_hook_del(uc, fetch_hook)); + OK(uc_close(uc)); +} + +static void test_arm_fetch_predicate_code_mutation_one(bool initial_z, + bool callback_z, + bool executes) +{ + const uint8_t code[] = { + 0x01, 0x00, 0xa0, 0x03, /* moveq r0,#1 */ + }; + ArmFetchPredicateTrace trace = { + .mutate_z = true, + .code_sets_z = callback_z, + }; + uint32_t cpsr; + uint32_t pc; + uint32_t r0 = 0; + uc_engine *uc; + uc_hook code_hook; + uc_hook fetch_hook; + + uc_common_setup(&uc, UC_ARCH_ARM, UC_MODE_ARM, (const char *)code, + sizeof(code), UC_CPU_ARM_CORTEX_A15); + OK(uc_reg_read(uc, UC_ARM_REG_CPSR, &cpsr)); + if (initial_z) { + cpsr |= 1U << 30; + } else { + cpsr &= ~(1U << 30); + } + OK(uc_reg_write(uc, UC_ARM_REG_CPSR, &cpsr)); + OK(uc_hook_add(uc, &fetch_hook, UC_HOOK_MEM_FETCH, + test_arm_fetch_predicate_callback, &trace, 1, 0)); + OK(uc_hook_add(uc, &code_hook, UC_HOOK_CODE, + test_arm_fetch_predicate_code_callback, &trace, 1, 0)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 1)); + + TEST_CHECK(trace.code_count == 1); + TEST_CHECK(trace.code_address[0] == code_start); + test_arm_fetch_assert_event(&trace, 0, ARM_FETCH_EVENT_CODE, code_start, + 4); + if (executes) { + TEST_CHECK(trace.fetch_count == 1); + TEST_CHECK(trace.event_count == 2); + test_arm_fetch_assert_event(&trace, 1, ARM_FETCH_EVENT_FETCH, + code_start, 4); + } else { + TEST_CHECK(trace.fetch_count == 0); + TEST_CHECK(trace.event_count == 1); + } + OK(uc_reg_read(uc, UC_ARM_REG_R0, &r0)); + OK(uc_reg_read(uc, UC_ARM_REG_PC, &pc)); + TEST_CHECK(r0 == (executes ? 1U : 0U)); + TEST_CHECK(pc == code_start + sizeof(code)); + + OK(uc_hook_del(uc, code_hook)); + OK(uc_hook_del(uc, fetch_hook)); + OK(uc_close(uc)); +} + +static void test_arm_fetch_predicate_code_mutation(void) +{ + test_arm_fetch_predicate_code_mutation_one(false, true, true); + test_arm_fetch_predicate_code_mutation_one(true, false, false); +} + static void test_arm_m_thumb_mrs(void) { uc_engine *uc; @@ -992,6 +1594,69 @@ static void test_arm_intr_capture_cb(uc_engine *uc, uint32_t intno, OK(uc_emu_stop(uc)); } +static void test_arm_m55_vlldm(void) +{ + const uint32_t control_fpca = 1U << 2; + const uint32_t control_sfpa = 1U << 3; + const uint32_t frame_addr = code_start + 0x1000; + const uint32_t frame_s0 = 0x0badc0de; + const uint32_t frame_s1 = 0xf00d1234; + const uint32_t frame_fpscr = 0xa0000000; + const uint32_t frame_vpr = 0x00fedcba; + uc_engine *uc; + uint8_t code[4]; + uint8_t frame[0x48] = { 0 }; + uint32_t control; + uint32_t fpscr = 0; + uint32_t s0 = 0; + uint32_t s1 = 0; + uint32_t vpr = 0; + uc_err err; + + test_arm_store_le(frame, 4, frame_s0); + test_arm_store_le(frame + 4, 4, frame_s1); + test_arm_store_le(frame + 0x40, 4, frame_fpscr); + test_arm_store_le(frame + 0x44, 4, frame_vpr); + test_arm_emit32(code, 0, 0x0a80ec30); /* vlldm r0 */ + + uc_common_setup(&uc, UC_ARCH_ARM, + UC_MODE_THUMB | UC_MODE_MCLASS, + (const char *)code, sizeof(code), + UC_CPU_ARM_CORTEX_M55); + test_arm_enable_vfp(uc); + test_arm_m_profile_activate_fp_context(uc); + OK(uc_mem_write(uc, frame_addr, frame, sizeof(frame))); + OK(uc_reg_write(uc, UC_ARM_REG_R0, &frame_addr)); + OK(uc_emu_start(uc, code_start | 1, + code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_ARM_REG_S0, &s0)); + OK(uc_reg_read(uc, UC_ARM_REG_S1, &s1)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_read(uc, UC_ARM_REG_VPR, &vpr)); + OK(uc_reg_read(uc, UC_ARM_REG_CONTROL, &control)); + TEST_CHECK_(s0 == frame_s0, "s0=0x%08x", s0); + TEST_CHECK_(s1 == frame_s1, "s1=0x%08x", s1); + TEST_CHECK_((fpscr & 0xf0000000) == frame_fpscr, + "fpscr=0x%08x", fpscr); + TEST_CHECK_(vpr == frame_vpr, "vpr=0x%08x", vpr); + TEST_CHECK_((control & (control_fpca | control_sfpa)) == + (control_fpca | control_sfpa), + "control=0x%08x", control); + OK(uc_close(uc)); + + uc_common_setup(&uc, UC_ARCH_ARM, + UC_MODE_THUMB | UC_MODE_MCLASS, + (const char *)code, sizeof(code), + UC_CPU_ARM_CORTEX_M33); + test_arm_enable_vfp(uc); + OK(uc_reg_write(uc, UC_ARM_REG_R0, &frame_addr)); + err = uc_emu_start(uc, code_start | 1, + code_start + sizeof(code), 0, 0); + TEST_CHECK_(err == UC_ERR_INSN_INVALID, "err=%u", (unsigned)err); + OK(uc_close(uc)); +} + static void test_arm_m55_vlstm_lazy_preserve(void) { const uint32_t frame_addr = code_start + 0x2000; @@ -1076,6 +1741,266 @@ static void test_arm_m55_vlstm_lazy_fault(void) OK(uc_close(uc)); } +static void test_arm_m55_fpcxt_s(void) +{ + const uint32_t control_fpca = 1U << 2; + const uint32_t control_sfpa = 1U << 3; + const uint32_t nzcv_mask = 0xf0000000; + const uint32_t fpdscr_ns_reset = 4U << 16; + const uint32_t data_addr = code_start + 0x1000; + const uint32_t fpscr_initial = 0x0a040000 | 0xa0000000; + const uint32_t fpcxt_write = 0x86040000; + const uint32_t expected_write = fpcxt_write & ~nzcv_mask; + uc_engine *uc; + uint8_t code[8]; + uint8_t mem[16] = { 0 }; + uint32_t control; + uint32_t expected_read; + uint32_t fpscr = fpscr_initial; + uint32_t r1 = data_addr; + uint32_t r2 = fpcxt_write; + uint32_t r3 = 0; + + test_arm_emit32(code, 0, 0x3a10eeff); /* vmrs r3,fpcxt_s */ + test_arm_emit32(code, 4, 0x2a10eeef); /* vmsr fpcxt_s,r2 */ + uc_common_setup(&uc, UC_ARCH_ARM, + UC_MODE_THUMB | UC_MODE_MCLASS, + (const char *)code, sizeof(code), + UC_CPU_ARM_CORTEX_M55); + test_arm_enable_vfp(uc); + test_arm_m_profile_activate_fp_context(uc); + OK(uc_reg_write(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_write(uc, UC_ARM_REG_R2, &r2)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + expected_read = (fpscr & ~nzcv_mask) | 0x80000000; + OK(uc_emu_start(uc, code_start | 1, + code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_ARM_REG_R3, &r3)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_read(uc, UC_ARM_REG_CONTROL, &control)); + TEST_CHECK_(r3 == expected_read, "r3=0x%08x expected=0x%08x", + r3, expected_read); + TEST_CHECK_((fpscr & ~nzcv_mask) == expected_write, + "fpscr=0x%08x expected=0x%08x", fpscr, + expected_write); + TEST_CHECK_((fpscr & nzcv_mask) == 0, "fpscr=0x%08x", fpscr); + TEST_CHECK_((control & (control_fpca | control_sfpa)) == + (control_fpca | control_sfpa), + "control=0x%08x", control); + OK(uc_close(uc)); + + test_arm_emit32(code, 0, 0xef80edc1); /* vstr fpcxt_s,[r1] */ + memset(mem, 0, sizeof(mem)); + fpscr = fpscr_initial; + uc_common_setup(&uc, UC_ARCH_ARM, + UC_MODE_THUMB | UC_MODE_MCLASS, + (const char *)code, 4, UC_CPU_ARM_CORTEX_M55); + test_arm_enable_vfp(uc); + test_arm_m_profile_activate_fp_context(uc); + OK(uc_mem_write(uc, data_addr, mem, sizeof(mem))); + OK(uc_reg_write(uc, UC_ARM_REG_R1, &r1)); + OK(uc_reg_write(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + expected_read = (fpscr & ~nzcv_mask) | 0x80000000; + OK(uc_emu_start(uc, code_start | 1, code_start + 4, 0, 0)); + OK(uc_mem_read(uc, data_addr, mem, sizeof(mem))); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_read(uc, UC_ARM_REG_CONTROL, &control)); + TEST_CHECK_(test_arm_load_le(mem, 4) == expected_read, + "stored=0x%08x expected=0x%08x", + test_arm_load_le(mem, 4), expected_read); + TEST_CHECK_((control & control_sfpa) == 0, + "control=0x%08x", control); + TEST_CHECK_((fpscr & ~nzcv_mask) == fpdscr_ns_reset, + "fpscr=0x%08x", fpscr); + OK(uc_close(uc)); + + test_arm_emit32(code, 0, 0xef80edd1); /* vldr fpcxt_s,[r1] */ + memset(mem, 0, sizeof(mem)); + test_arm_store_le(mem, 4, fpcxt_write); + fpscr = 0; + uc_common_setup(&uc, UC_ARCH_ARM, + UC_MODE_THUMB | UC_MODE_MCLASS, + (const char *)code, 4, UC_CPU_ARM_CORTEX_M55); + test_arm_enable_vfp(uc); + test_arm_m_profile_activate_fp_context(uc); + OK(uc_mem_write(uc, data_addr, mem, sizeof(mem))); + OK(uc_reg_write(uc, UC_ARM_REG_R1, &r1)); + OK(uc_reg_write(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_emu_start(uc, code_start | 1, code_start + 4, 0, 0)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_read(uc, UC_ARM_REG_CONTROL, &control)); + TEST_CHECK_((control & control_sfpa) != 0, + "control=0x%08x", control); + TEST_CHECK_((fpscr & ~nzcv_mask) == expected_write, + "fpscr=0x%08x expected=0x%08x", fpscr, + expected_write); + OK(uc_close(uc)); +} + +static void test_arm_m55_fpcxt_ns(void) +{ + const uint32_t control_fpca = 1U << 2; + const uint32_t control_sfpa = 1U << 3; + const uint32_t nzcv_mask = 0xf0000000; + const uint32_t fpdscr_ns_reset = 4U << 16; + const uint32_t data_addr = code_start + 0x1000; + const uint32_t fpscr_initial = 0x0a040000 | 0xa0000000; + const uint32_t fpcxt_write = 0x86040000; + const uint32_t expected_write = fpcxt_write & ~nzcv_mask; + uc_engine *uc; + uint8_t code[8]; + uint8_t mem[16] = { 0 }; + uint32_t control = 0; + uint32_t expected_fpscr; + uint32_t expected_read; + uint32_t fpscr; + uint32_t r0 = fpcxt_write; + uint32_t r1 = 0; + uint32_t r2; + uc_err err; + + test_arm_emit32(code, 0, 0x1a10eefe); /* vmrs r1,fpcxt_ns */ + test_arm_emit32(code, 4, 0x0a10eeee); /* vmsr fpcxt_ns,r0 */ + fpscr = 0x01030000; + uc_common_setup(&uc, UC_ARCH_ARM, + UC_MODE_THUMB | UC_MODE_MCLASS, + (const char *)code, sizeof(code), + UC_CPU_ARM_CORTEX_M55); + test_arm_enable_vfp(uc); + OK(uc_reg_write(uc, UC_ARM_REG_CONTROL, &control)); + OK(uc_reg_write(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_write(uc, UC_ARM_REG_R0, &r0)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + expected_fpscr = fpscr & ~nzcv_mask; + OK(uc_emu_start(uc, code_start | 1, + code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_ARM_REG_R1, &r1)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_read(uc, UC_ARM_REG_CONTROL, &control)); + TEST_CHECK_(r1 == fpdscr_ns_reset, "r1=0x%08x", r1); + TEST_CHECK_((fpscr & ~nzcv_mask) == expected_fpscr, + "fpscr=0x%08x expected=0x%08x", fpscr, + expected_fpscr); + TEST_CHECK_((control & (control_fpca | control_sfpa)) == 0, + "control=0x%08x", control); + OK(uc_close(uc)); + + control = control_fpca; + fpscr = fpscr_initial; + r1 = 0; + uc_common_setup(&uc, UC_ARCH_ARM, + UC_MODE_THUMB | UC_MODE_MCLASS, + (const char *)code, sizeof(code), + UC_CPU_ARM_CORTEX_M55); + test_arm_enable_vfp(uc); + OK(uc_reg_write(uc, UC_ARM_REG_CONTROL, &control)); + OK(uc_reg_write(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_write(uc, UC_ARM_REG_R0, &r0)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + expected_read = fpscr & ~nzcv_mask; + OK(uc_emu_start(uc, code_start | 1, + code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_ARM_REG_R1, &r1)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_read(uc, UC_ARM_REG_CONTROL, &control)); + TEST_CHECK_(r1 == expected_read, "r1=0x%08x expected=0x%08x", + r1, expected_read); + TEST_CHECK_((fpscr & ~nzcv_mask) == expected_write, + "fpscr=0x%08x expected=0x%08x", fpscr, + expected_write); + TEST_CHECK_((control & (control_fpca | control_sfpa)) == + (control_fpca | control_sfpa), + "control=0x%08x", control); + OK(uc_close(uc)); + + test_arm_emit32(code, 0, 0xcf80edc1); /* vstr fpcxt_ns,[r1] */ + memset(mem, 0, sizeof(mem)); + control = control_fpca; + fpscr = fpscr_initial; + r1 = data_addr; + uc_common_setup(&uc, UC_ARCH_ARM, + UC_MODE_THUMB | UC_MODE_MCLASS, + (const char *)code, 4, UC_CPU_ARM_CORTEX_M55); + test_arm_enable_vfp(uc); + OK(uc_mem_write(uc, data_addr, mem, sizeof(mem))); + OK(uc_reg_write(uc, UC_ARM_REG_CONTROL, &control)); + OK(uc_reg_write(uc, UC_ARM_REG_R1, &r1)); + OK(uc_reg_write(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + expected_read = fpscr & ~nzcv_mask; + OK(uc_emu_start(uc, code_start | 1, code_start + 4, 0, 0)); + OK(uc_mem_read(uc, data_addr, mem, sizeof(mem))); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + TEST_CHECK_(test_arm_load_le(mem, 4) == expected_read, + "stored=0x%08x expected=0x%08x", + test_arm_load_le(mem, 4), expected_read); + TEST_CHECK_((fpscr & ~nzcv_mask) == fpdscr_ns_reset, + "fpscr=0x%08x", fpscr); + OK(uc_close(uc)); + + test_arm_emit32(code, 0, 0xcf81ecf2); /* vldr fpcxt_ns,[r2],#4 */ + memset(mem, 0, sizeof(mem)); + test_arm_store_le(mem, 4, fpcxt_write); + control = 0; + fpscr = 0x04460000; + r2 = data_addr; + uc_common_setup(&uc, UC_ARCH_ARM, + UC_MODE_THUMB | UC_MODE_MCLASS, + (const char *)code, 4, UC_CPU_ARM_CORTEX_M55); + test_arm_enable_vfp(uc); + OK(uc_mem_write(uc, data_addr, mem, sizeof(mem))); + OK(uc_reg_write(uc, UC_ARM_REG_CONTROL, &control)); + OK(uc_reg_write(uc, UC_ARM_REG_R2, &r2)); + OK(uc_reg_write(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + expected_fpscr = fpscr & ~nzcv_mask; + OK(uc_emu_start(uc, code_start | 1, code_start + 4, 0, 0)); + OK(uc_reg_read(uc, UC_ARM_REG_R2, &r2)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + TEST_CHECK_(r2 == data_addr + 4, "r2=0x%08x", r2); + TEST_CHECK_((fpscr & ~nzcv_mask) == expected_fpscr, + "fpscr=0x%08x expected=0x%08x", fpscr, + expected_fpscr); + OK(uc_close(uc)); + + test_arm_emit32(code, 0, 0xcf81ece2); /* vstr fpcxt_ns,[r2],#4 */ + memset(mem, 0, sizeof(mem)); + control = 0; + fpscr = 0x04460000; + r2 = data_addr; + uc_common_setup(&uc, UC_ARCH_ARM, + UC_MODE_THUMB | UC_MODE_MCLASS, + (const char *)code, 4, UC_CPU_ARM_CORTEX_M55); + test_arm_enable_vfp(uc); + OK(uc_mem_write(uc, data_addr, mem, sizeof(mem))); + OK(uc_reg_write(uc, UC_ARM_REG_CONTROL, &control)); + OK(uc_reg_write(uc, UC_ARM_REG_R2, &r2)); + OK(uc_reg_write(uc, UC_ARM_REG_FPSCR, &fpscr)); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + expected_fpscr = fpscr & ~nzcv_mask; + OK(uc_emu_start(uc, code_start | 1, code_start + 4, 0, 0)); + OK(uc_reg_read(uc, UC_ARM_REG_R2, &r2)); + OK(uc_mem_read(uc, data_addr, mem, sizeof(mem))); + OK(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpscr)); + TEST_CHECK_(r2 == data_addr + 4, "r2=0x%08x", r2); + TEST_CHECK_(test_arm_load_le(mem, 4) == fpdscr_ns_reset, + "stored=0x%08x", test_arm_load_le(mem, 4)); + TEST_CHECK_((fpscr & ~nzcv_mask) == expected_fpscr, + "fpscr=0x%08x expected=0x%08x", fpscr, + expected_fpscr); + OK(uc_close(uc)); + + test_arm_emit32(code, 0, 0x1a10eefe); /* vmrs r1,fpcxt_ns */ + uc_common_setup(&uc, UC_ARCH_ARM, + UC_MODE_THUMB | UC_MODE_MCLASS, + (const char *)code, 4, UC_CPU_ARM_CORTEX_M33); + test_arm_enable_vfp(uc); + err = uc_emu_start(uc, code_start | 1, code_start + 4, 0, 0); + TEST_CHECK_(err == UC_ERR_INSN_INVALID, "err=%u", (unsigned)err); + OK(uc_close(uc)); +} + static void test_arm_m55_sysreg_mem(void) { const uint32_t fpcr_ahp = 1U << 26; @@ -1442,11 +2367,10 @@ static void test_arm_mve_store_masked(uint8_t *expected, size_t offset, uint8_t lane[8]; unsigned b; - if (size == 8) { - test_arm_store_le64(lane, value); - } else { - test_arm_store_le(lane, size, (uint32_t)value); + if (!TEST_CHECK(size <= sizeof(lane))) { + return; } + test_arm_store_le64(lane, value); for (b = 0; b < size; b++) { if (mask & (1U << (offset + b))) { expected[offset + b] = lane[b]; @@ -11139,7 +12063,7 @@ static void test_arm_m55_mve_vcmp_fp(void) // EXC_RETURN. We can't help user handle EXC_RETURN since unicorn is designed // not to handle any CPU exception. // -static void test_arm_m_exc_return_hook_interrupt(uc_engine *uc, int intno, +static void test_arm_m_exc_return_hook_interrupt(uc_engine *uc, uint32_t intno, void *data) { int r_pc; @@ -11632,10 +12556,14 @@ static void test_arm_context_save(void) char code[] = "\x83\xb0"; // sub sp, #0xc uc_context *ctx; uint32_t pc; + uint32_t saved_r0 = 0x1a2b3c4d; + uint32_t target_r0 = 0xa5a5a5a5; + uint32_t read_r0; uc_common_setup(&uc, UC_ARCH_ARM, UC_MODE_THUMB, code, sizeof(code) - 1, UC_CPU_ARM_CORTEX_R5); + OK(uc_reg_write(uc, UC_ARM_REG_R0, &saved_r0)); OK(uc_context_alloc(uc, &ctx)); OK(uc_context_save(uc, ctx)); OK(uc_context_reg_read(ctx, UC_ARM_REG_PC, (void *)&pc)); @@ -11643,15 +12571,511 @@ static void test_arm_context_save(void) OK(uc_context_restore(uc, ctx)); uc_common_setup(&uc2, UC_ARCH_ARM, UC_MODE_THUMB, code, sizeof(code) - 1, - UC_CPU_ARM_CORTEX_A7); // Note the different CPU model + UC_CPU_ARM_CORTEX_A7); + TEST_CHECK(uc_context_size(uc) != uc_context_size(uc2)); + OK(uc_reg_write(uc2, UC_ARM_REG_R0, &target_r0)); + OK(uc_close(uc)); OK(uc_context_restore(uc2, ctx)); + OK(uc_reg_read(uc2, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == saved_r0); OK(uc_context_free(ctx)); - OK(uc_close(uc)); OK(uc_close(uc2)); } +static void test_arm_context_cross_engine_after_source_close(void) +{ + const uc_mode mode = UC_MODE_THUMB | UC_MODE_MCLASS; + uc_engine *source; + uc_engine *destination; + uc_context *ctx; + uint32_t saved_r0 = 0x12345678; + uint32_t target_r0 = 0x87654321; + uint32_t read_r0; + + OK(uc_open(UC_ARCH_ARM, mode, &source)); + OK(uc_ctl_set_cpu_model(source, UC_CPU_ARM_CORTEX_M33)); + OK(uc_open(UC_ARCH_ARM, mode, &destination)); + OK(uc_ctl_set_cpu_model(destination, UC_CPU_ARM_CORTEX_M33)); + + OK(uc_reg_write(source, UC_ARM_REG_R0, &saved_r0)); + OK(uc_context_alloc(source, &ctx)); + OK(uc_context_save(source, ctx)); + OK(uc_reg_write(destination, UC_ARM_REG_R0, &target_r0)); + OK(uc_close(source)); + + OK(uc_context_restore(destination, ctx)); + OK(uc_reg_read(destination, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == saved_r0); + + OK(uc_context_free(ctx)); + OK(uc_close(destination)); +} + +static void test_arm_context_serialized_restore(void) +{ + const uc_mode mode = UC_MODE_THUMB | UC_MODE_MCLASS; + uc_engine *source; + uc_engine *destination; + uc_context *source_ctx; + uc_context *restored_ctx; + uint8_t *serialized; + size_t context_size; + size_t payload_size; + uint32_t saved_r0 = 0x0badc0de; + uint32_t target_r0 = 0xf00dcafe; + uint32_t read_r0; + + OK(uc_open(UC_ARCH_ARM, mode, &source)); + OK(uc_ctl_set_cpu_model(source, UC_CPU_ARM_CORTEX_M33)); + OK(uc_reg_write(source, UC_ARM_REG_R0, &saved_r0)); + context_size = uc_context_size(source); + serialized = malloc(context_size); + TEST_ASSERT(serialized != NULL); + OK(uc_context_alloc(source, &source_ctx)); + OK(uc_context_save(source, source_ctx)); + memcpy(serialized, source_ctx, context_size); + OK(uc_context_free(source_ctx)); + OK(uc_close(source)); + + OK(uc_open(UC_ARCH_ARM, mode, &destination)); + OK(uc_ctl_set_cpu_model(destination, UC_CPU_ARM_CORTEX_M33)); + TEST_CHECK(uc_context_size(destination) == context_size); + OK(uc_context_alloc(destination, &restored_ctx)); + memcpy(restored_ctx, serialized, context_size); + + OK(uc_context_restore(destination, restored_ctx)); + OK(uc_reg_read(destination, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == saved_r0); + + OK(uc_reg_write(destination, UC_ARM_REG_R0, &target_r0)); + memcpy(&payload_size, restored_ctx, sizeof(payload_size)); + TEST_ASSERT(payload_size != 0); + payload_size--; + memcpy(restored_ctx, &payload_size, sizeof(payload_size)); + uc_assert_err(UC_ERR_ARG, + uc_context_restore(destination, restored_ctx)); + OK(uc_reg_read(destination, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == target_r0); + + free(serialized); + OK(uc_context_free(restored_ctx)); + OK(uc_close(destination)); +} + +static void test_arm_context_incompatible_mode(void) +{ + uc_engine *arm; + uc_engine *thumb; + uc_context *ctx; + uint32_t saved_r0 = 0x11223344; + uint32_t target_r0 = 0x55667788; + uint32_t read_r0; + + OK(uc_open(UC_ARCH_ARM, UC_MODE_ARM, &arm)); + OK(uc_ctl_set_cpu_model(arm, UC_CPU_ARM_CORTEX_A15)); + OK(uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &thumb)); + OK(uc_ctl_set_cpu_model(thumb, UC_CPU_ARM_CORTEX_A15)); + TEST_CHECK(uc_context_size(arm) == uc_context_size(thumb)); + + OK(uc_reg_write(arm, UC_ARM_REG_R0, &saved_r0)); + OK(uc_context_alloc(arm, &ctx)); + OK(uc_context_save(arm, ctx)); + OK(uc_reg_write(thumb, UC_ARM_REG_R0, &target_r0)); + + uc_assert_err(UC_ERR_ARG, uc_context_save(thumb, ctx)); + OK(uc_context_reg_read(ctx, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == saved_r0); + OK(uc_reg_read(thumb, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == target_r0); + + uc_assert_err(UC_ERR_ARG, uc_context_restore(thumb, ctx)); + OK(uc_reg_read(thumb, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == target_r0); + + OK(uc_context_free(ctx)); + OK(uc_close(arm)); + OK(uc_close(thumb)); +} + +static void test_arm_context_cross_model_size(void) +{ + const uc_mode mode = UC_MODE_THUMB | UC_MODE_MCLASS; + uc_engine *m0; + uc_engine *m33; + uc_context *m0_ctx; + uc_context *m33_ctx; + uint32_t m0_saved_r0 = 0x10203040; + uint32_t m33_saved_r0 = 0x50607080; + uint32_t m0_target_r0 = 0x90a0b0c0; + uint32_t m33_target_r0 = 0xd0e0f000; + uint32_t read_r0; + + OK(uc_open(UC_ARCH_ARM, mode, &m0)); + OK(uc_ctl_set_cpu_model(m0, UC_CPU_ARM_CORTEX_M0)); + OK(uc_open(UC_ARCH_ARM, mode, &m33)); + OK(uc_ctl_set_cpu_model(m33, UC_CPU_ARM_CORTEX_M33)); + TEST_CHECK(uc_context_size(m0) != uc_context_size(m33)); + + OK(uc_reg_write(m0, UC_ARM_REG_R0, &m0_saved_r0)); + OK(uc_context_alloc(m0, &m0_ctx)); + OK(uc_context_save(m0, m0_ctx)); + OK(uc_reg_write(m33, UC_ARM_REG_R0, &m33_saved_r0)); + OK(uc_context_alloc(m33, &m33_ctx)); + OK(uc_context_save(m33, m33_ctx)); + OK(uc_reg_write(m0, UC_ARM_REG_R0, &m0_target_r0)); + OK(uc_reg_write(m33, UC_ARM_REG_R0, &m33_target_r0)); + + uc_assert_err(UC_ERR_ARG, uc_context_save(m33, m0_ctx)); + OK(uc_context_reg_read(m0_ctx, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == m0_saved_r0); + OK(uc_reg_read(m33, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == m33_target_r0); + OK(uc_context_restore(m33, m0_ctx)); + OK(uc_reg_read(m33, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == m0_saved_r0); + + OK(uc_context_restore(m0, m33_ctx)); + OK(uc_reg_read(m0, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == m33_saved_r0); + + OK(uc_reg_write(m0, UC_ARM_REG_R0, &m0_target_r0)); + OK(uc_context_save(m0, m33_ctx)); + OK(uc_context_reg_read(m33_ctx, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == m0_target_r0); + + OK(uc_reg_write(m0, UC_ARM_REG_R0, &m33_target_r0)); + OK(uc_context_restore(m0, m33_ctx)); + OK(uc_reg_read(m0, UC_ARM_REG_R0, &read_r0)); + TEST_CHECK(read_r0 == m0_target_r0); + + OK(uc_context_free(m0_ctx)); + OK(uc_context_free(m33_ctx)); + OK(uc_close(m0)); + OK(uc_close(m33)); +} + +static void test_arm_memory_context_cross_owner_save_unchanged(void) +{ + const uc_mode mode = UC_MODE_THUMB | UC_MODE_MCLASS; + const uint64_t address = 0x8000; + uc_engine *source; + uc_engine *other; + uc_context *context; + uint8_t *before; + size_t context_size; + + OK(uc_open(UC_ARCH_ARM, mode, &source)); + OK(uc_ctl_set_cpu_model(source, UC_CPU_ARM_CORTEX_M33)); + OK(uc_open(UC_ARCH_ARM, mode, &other)); + OK(uc_ctl_set_cpu_model(other, UC_CPU_ARM_CORTEX_M0)); + TEST_CHECK(uc_context_size(source) > uc_context_size(other)); + OK(uc_ctl_context_mode(source, UC_CTL_CONTEXT_MEMORY)); + OK(uc_ctl_context_mode(other, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(source, address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_map(other, address, 0x1000, UC_PROT_ALL)); + + context_size = uc_context_size(source); + before = malloc(context_size); + TEST_ASSERT(before != NULL); + OK(uc_context_alloc(source, &context)); + OK(uc_context_save(source, context)); + memcpy(before, context, context_size); + + uc_assert_err(UC_ERR_ARG, uc_context_save(other, context)); + TEST_CHECK(memcmp(before, context, context_size) == 0); + + free(before); + OK(uc_context_free(context)); + OK(uc_close(source)); + OK(uc_close(other)); +} + +static void test_arm_context_cross_model_features(void) +{ + const char code[] = "\x00\xf0\x04\x00"; /* and.w r0,r0,#4 */ + uc_engine *source; + uc_engine *destination; + uc_context *ctx; + uint32_t r0 = 0x24; + + uc_common_setup(&source, UC_ARCH_ARM, UC_MODE_THUMB, code, + sizeof(code) - 1, UC_CPU_ARM_926); + uc_common_setup(&destination, UC_ARCH_ARM, UC_MODE_THUMB, code, + sizeof(code) - 1, UC_CPU_ARM_CORTEX_A15); + OK(uc_reg_write(source, UC_ARM_REG_R0, &r0)); + OK(uc_context_alloc(source, &ctx)); + OK(uc_context_save(source, ctx)); + OK(uc_close(source)); + + OK(uc_context_restore(destination, ctx)); + OK(uc_emu_start(destination, code_start | 1, + code_start + sizeof(code) - 1, 0, 0)); + OK(uc_reg_read(destination, UC_ARM_REG_R0, &r0)); + TEST_CHECK_(r0 == 4, "r0=0x%08x", r0); + + OK(uc_context_free(ctx)); + OK(uc_close(destination)); +} + +static void test_arm_context_source_close_debug_state(void) +{ + const uint32_t breakpoint_address = (uint32_t)code_start + 20; + const uint32_t watchpoint_address = (uint32_t)code_start + 0x100; + const uint32_t breakpoint_control = 1U | (1U << 1) | (0xfU << 5); + const uint32_t watchpoint_control = + 1U | (1U << 1) | (2U << 3) | (0xfU << 5); + const uint32_t mdscr = 1U << 15; + const uint32_t stored_value = 0x12345678; + ArmIntrCapture capture = { 0 }; + uc_engine *source; + uc_engine *destination; + uc_context *ctx; + uc_hook hook; + uint8_t code[28]; + + test_arm_emit32(code, 0, 0xee000e90); /* mcr p14,0,r0,c0,c0,4 */ + test_arm_emit32(code, 4, 0xee001eb0); /* mcr p14,0,r1,c0,c0,5 */ + test_arm_emit32(code, 8, 0xee002ed0); /* mcr p14,0,r2,c0,c0,6 */ + test_arm_emit32(code, 12, 0xee003ef0); /* mcr p14,0,r3,c0,c0,7 */ + test_arm_emit32(code, 16, 0xee004e52); /* mcr p14,0,r4,c0,c2,2 */ + test_arm_emit32(code, 20, 0xe1a05005); /* mov r5,r5 */ + test_arm_emit32(code, 24, 0xe5826000); /* str r6,[r2] */ + + uc_common_setup(&source, UC_ARCH_ARM, UC_MODE_ARM, (const char *)code, + sizeof(code), UC_CPU_ARM_CORTEX_A15); + uc_common_setup(&destination, UC_ARCH_ARM, UC_MODE_ARM, + (const char *)code, sizeof(code), + UC_CPU_ARM_CORTEX_A15); + OK(uc_reg_write(source, UC_ARM_REG_R0, &breakpoint_address)); + OK(uc_reg_write(source, UC_ARM_REG_R1, &breakpoint_control)); + OK(uc_reg_write(source, UC_ARM_REG_R2, &watchpoint_address)); + OK(uc_reg_write(source, UC_ARM_REG_R3, &watchpoint_control)); + OK(uc_reg_write(source, UC_ARM_REG_R4, &mdscr)); + OK(uc_reg_write(source, UC_ARM_REG_R6, &stored_value)); + OK(uc_emu_start(source, code_start, breakpoint_address, 0, 5)); + TEST_CHECK(test_arm_cp14_read(source, 0, 4) == breakpoint_address); + TEST_CHECK(test_arm_cp14_read(source, 0, 5) == breakpoint_control); + TEST_CHECK(test_arm_cp14_read(source, 0, 6) == watchpoint_address); + TEST_CHECK(test_arm_cp14_read(source, 0, 7) == watchpoint_control); + TEST_CHECK(test_arm_cp14_read(source, 2, 2) == mdscr); + OK(uc_context_alloc(source, &ctx)); + OK(uc_context_save(source, ctx)); + OK(uc_hook_add(source, &hook, UC_HOOK_INTR, + test_arm_intr_capture_cb, &capture, 1, 0)); + OK(uc_emu_start(source, breakpoint_address, + breakpoint_address + 4, 0, 1)); + TEST_CHECK_(capture.count == 1, + "source breakpoint count=%u intno=%u", + capture.count, capture.intno); + + capture.count = 0; + capture.intno = 0; + OK(uc_emu_start(source, breakpoint_address + 4, + breakpoint_address + 8, 0, 1)); + TEST_CHECK_(capture.count == 1, + "source watchpoint count=%u intno=%u", + capture.count, capture.intno); + + capture.count = 0; + capture.intno = 0; + OK(uc_close(source)); + + OK(uc_context_restore(destination, ctx)); + OK(uc_hook_add(destination, &hook, UC_HOOK_INTR, + test_arm_intr_capture_cb, &capture, 1, 0)); + OK(uc_emu_start(destination, breakpoint_address, + breakpoint_address + 4, 0, 1)); + TEST_CHECK_(capture.count == 1, "breakpoint count=%u intno=%u", + capture.count, capture.intno); + + capture.count = 0; + capture.intno = 0; + OK(uc_emu_start(destination, breakpoint_address + 4, + breakpoint_address + 8, 0, 1)); + TEST_CHECK_(capture.count == 1, "watchpoint count=%u intno=%u", + capture.count, capture.intno); + + OK(uc_context_free(ctx)); + OK(uc_close(destination)); +} + +static void test_arm_context_cross_model_debug_gating(void) +{ + const uint32_t breakpoint_address = (uint32_t)code_start + 20; + const uint32_t watchpoint_address = (uint32_t)code_start + 0x100; + const uint32_t breakpoint_control = 1U | (1U << 1) | (0xfU << 5); + const uint32_t watchpoint_control = + 1U | (1U << 1) | (2U << 3) | (0xfU << 5); + const uint32_t mdscr = 1U << 15; + const uint32_t stored_value = 0x12345678; + ArmIntrCapture capture = { 0 }; + uc_engine *source; + uc_engine *a8; + uc_engine *arm926; + uc_context *ctx; + uc_hook hook; + uint32_t actual; + uint8_t code[28]; + + test_arm_emit32(code, 0, 0xee000e95); /* mcr p14,0,r0,c0,c5,4 */ + test_arm_emit32(code, 4, 0xee001eb5); /* mcr p14,0,r1,c0,c5,5 */ + test_arm_emit32(code, 8, 0xee002ed3); /* mcr p14,0,r2,c0,c3,6 */ + test_arm_emit32(code, 12, 0xee003ef3); /* mcr p14,0,r3,c0,c3,7 */ + test_arm_emit32(code, 16, 0xee004e52); /* mcr p14,0,r4,c0,c2,2 */ + test_arm_emit32(code, 20, 0xe1a05005); /* mov r5,r5 */ + test_arm_emit32(code, 24, 0xe5826000); /* str r6,[r2] */ + + uc_common_setup(&source, UC_ARCH_ARM, UC_MODE_ARM, (const char *)code, + sizeof(code), UC_CPU_ARM_CORTEX_A15); + uc_common_setup(&a8, UC_ARCH_ARM, UC_MODE_ARM, (const char *)code, + sizeof(code), UC_CPU_ARM_CORTEX_A8); + uc_common_setup(&arm926, UC_ARCH_ARM, UC_MODE_ARM, (const char *)code, + sizeof(code), UC_CPU_ARM_926); + OK(uc_reg_write(source, UC_ARM_REG_R0, &breakpoint_address)); + OK(uc_reg_write(source, UC_ARM_REG_R1, &breakpoint_control)); + OK(uc_reg_write(source, UC_ARM_REG_R2, &watchpoint_address)); + OK(uc_reg_write(source, UC_ARM_REG_R3, &watchpoint_control)); + OK(uc_reg_write(source, UC_ARM_REG_R4, &mdscr)); + OK(uc_reg_write(source, UC_ARM_REG_R6, &stored_value)); + OK(uc_emu_start(source, code_start, breakpoint_address, 0, 5)); + TEST_CHECK(test_arm_cp14_read(source, 5, 4) == breakpoint_address); + TEST_CHECK(test_arm_cp14_read(source, 5, 5) == breakpoint_control); + TEST_CHECK(test_arm_cp14_read(source, 3, 6) == watchpoint_address); + TEST_CHECK(test_arm_cp14_read(source, 3, 7) == watchpoint_control); + OK(uc_context_alloc(source, &ctx)); + OK(uc_context_save(source, ctx)); + OK(uc_hook_add(source, &hook, UC_HOOK_INTR, + test_arm_intr_capture_cb, &capture, 1, 0)); + + OK(uc_emu_start(source, breakpoint_address, + breakpoint_address + 4, 0, 1)); + TEST_CHECK_(capture.count == 1, + "source breakpoint count=%u intno=%u", + capture.count, capture.intno); + capture.count = 0; + capture.intno = 0; + OK(uc_emu_start(source, breakpoint_address + 4, + breakpoint_address + 8, 0, 1)); + TEST_CHECK_(capture.count == 1, + "source watchpoint count=%u intno=%u", + capture.count, capture.intno); + capture.count = 0; + capture.intno = 0; + OK(uc_close(source)); + + OK(uc_context_restore(a8, ctx)); + OK(uc_hook_add(a8, &hook, UC_HOOK_INTR, test_arm_intr_capture_cb, + &capture, 1, 0)); + OK(uc_emu_start(a8, breakpoint_address, + breakpoint_address + 4, 0, 1)); + TEST_CHECK_(capture.count == 1, + "A8 breakpoint count=%u intno=%u", + capture.count, capture.intno); + capture.count = 0; + capture.intno = 0; + OK(uc_emu_start(a8, breakpoint_address + 4, + breakpoint_address + 8, 0, 1)); + TEST_CHECK_(capture.count == 0, + "A8 unsupported watchpoint count=%u intno=%u", + capture.count, capture.intno); + OK(uc_mem_read(a8, watchpoint_address, &actual, sizeof(actual))); + TEST_CHECK(actual == stored_value); + OK(uc_close(a8)); + + capture.count = 0; + capture.intno = 0; + OK(uc_context_restore(arm926, ctx)); + OK(uc_hook_add(arm926, &hook, UC_HOOK_INTR, + test_arm_intr_capture_cb, &capture, 1, 0)); + OK(uc_emu_start(arm926, breakpoint_address, + breakpoint_address + 4, 0, 1)); + TEST_CHECK_(capture.count == 0, + "ARM926 unsupported breakpoint count=%u intno=%u", + capture.count, capture.intno); + capture.count = 0; + capture.intno = 0; + OK(uc_emu_start(arm926, breakpoint_address + 4, + breakpoint_address + 8, 0, 1)); + TEST_CHECK_(capture.count == 0, + "ARM926 unsupported watchpoint count=%u intno=%u", + capture.count, capture.intno); + OK(uc_mem_read(arm926, watchpoint_address, &actual, sizeof(actual))); + TEST_CHECK(actual == stored_value); + + OK(uc_context_free(ctx)); + OK(uc_close(arm926)); +} + +static void test_arm_context_malformed_cpu_memory_atomic(void) +{ + const uint32_t data_address = (uint32_t)code_start + 0x2000; + const uint32_t saved_memory = 0x11223344; + const uint32_t current_memory = 0xaabbccdd; + const uint32_t saved_r0 = 0x12345678; + const uint32_t current_r0 = 0x87654321; + const char code[] = "\x00\xf0\x20\xe3"; /* nop */ + uc_engine *uc; + uc_context *ctx; + size_t payload_size; + uint32_t actual; + + uc_common_setup(&uc, UC_ARCH_ARM, + UC_MODE_THUMB | UC_MODE_MCLASS, code, + sizeof(code) - 1, UC_CPU_ARM_CORTEX_M33); + OK(uc_ctl_context_mode(uc, + UC_CTL_CONTEXT_CPU | UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_write(uc, data_address, &saved_memory, sizeof(saved_memory))); + OK(uc_reg_write(uc, UC_ARM_REG_R0, &saved_r0)); + OK(uc_context_alloc(uc, &ctx)); + OK(uc_context_save(uc, ctx)); + + OK(uc_mem_write(uc, data_address, ¤t_memory, + sizeof(current_memory))); + OK(uc_reg_write(uc, UC_ARM_REG_R0, ¤t_r0)); + memcpy(&payload_size, ctx, sizeof(payload_size)); + TEST_ASSERT(payload_size != 0); + payload_size--; + memcpy(ctx, &payload_size, sizeof(payload_size)); + + uc_assert_err(UC_ERR_ARG, uc_context_restore(uc, ctx)); + OK(uc_mem_read(uc, data_address, &actual, sizeof(actual))); + TEST_CHECK_(actual == current_memory, "memory=0x%08x", actual); + OK(uc_reg_read(uc, UC_ARM_REG_R0, &actual)); + TEST_CHECK_(actual == current_r0, "r0=0x%08x", actual); + + OK(uc_context_free(ctx)); + OK(uc_close(uc)); +} + +static void test_arm_context_raw_buffer(void) +{ + const uint32_t saved_r0 = 0x12345678; + const uint32_t current_r0 = 0x87654321; + uc_engine *uc; + uc_context *ctx; + size_t context_size; + uint32_t actual; + + OK(uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_ARM_CORTEX_A15)); + context_size = uc_context_size(uc); + ctx = (uc_context *)malloc(context_size); + TEST_ASSERT(ctx != NULL); + memset(ctx, 0xa5, context_size); + + OK(uc_reg_write(uc, UC_ARM_REG_R0, &saved_r0)); + OK(uc_context_save(uc, ctx)); + OK(uc_reg_write(uc, UC_ARM_REG_R0, ¤t_r0)); + OK(uc_context_restore(uc, ctx)); + OK(uc_reg_read(uc, UC_ARM_REG_R0, &actual)); + TEST_CHECK_(actual == saved_r0, "r0=0x%08x", actual); + + free(ctx); + OK(uc_close(uc)); +} + static void test_arm_thumb2(void) { uc_engine *uc; @@ -11692,11 +13116,16 @@ static void test_armeb_be32_thumb2(void) OK(uc_close(uc)); } -static bool test_arm_mem_read_write_cb(uc_engine *uc, int type, - uint64_t address, int size, - int64_t value, void *user_data) +static void test_arm_mem_read_write_cb(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) { uint64_t *count = (uint64_t *)user_data; + uint64_t total = count[0] + count[1]; + uint32_t pc; + + OK(uc_reg_read(uc, UC_ARM_REG_PC, &pc)); + TEST_CHECK(pc == code_start + (total % 4) * 4); switch (type) { case UC_MEM_READ: count[0]++; @@ -11706,7 +13135,6 @@ static bool test_arm_mem_read_write_cb(uc_engine *uc, int type, break; } - return 0; } static void test_arm_mem_hook_read_write(void) { @@ -11721,6 +13149,7 @@ static void test_arm_mem_hook_read_write(void) r_sp = 0x9000; uc_hook hk; uint64_t counter[2] = {0, 0}; + size_t i; uc_common_setup(&uc, UC_ARCH_ARM, UC_MODE_ARM, code, sizeof(code) - 1, UC_CPU_ARM_CORTEX_A15); @@ -11733,9 +13162,78 @@ static void test_arm_mem_hook_read_write(void) OK(uc_hook_add(uc, &hk, UC_HOOK_MEM_WRITE, test_arm_mem_read_write_cb, counter, 1, 0)); - OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); + for (i = 0; i < 8; i++) { + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); + } + + TEST_CHECK(counter[0] == 16 && counter[1] == 16); + OK(uc_close(uc)); +} + +typedef struct ArmNestedFlushHookData { + uint64_t nested_address; + uint32_t count; + bool nested_started; +} ArmNestedFlushHookData; + +static void test_arm_memory_hook_nested_flush_callback( + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) +{ + ArmNestedFlushHookData *data = + (ArmNestedFlushHookData *)user_data; + + data->count++; + if (!data->nested_started) { + data->nested_started = true; + OK(uc_ctl_flush_tb(uc)); + OK(uc_emu_start(uc, data->nested_address, + data->nested_address + 4, 0, 0)); + } +} + +static void test_arm_memory_hook_nested_tb_flush(void) +{ + const uint64_t data_address = 0x200000; + const uint64_t nested_address = code_start + 0x1000; + const uint8_t outer_code[] = { + 0x00, 0x10, 0x90, 0xe5, /* ldr r1, [r0] */ + 0x01, 0x10, 0x81, 0xe2, /* add r1, r1, #1 */ + }; + const uint8_t nested_code[] = { + 0x07, 0x20, 0xa0, 0xe3, /* mov r2, #7 */ + }; + const uint32_t memory_value = 0x11223344; + ArmNestedFlushHookData data = { + .nested_address = nested_address, + }; + uint32_t r0 = (uint32_t)data_address; + uint32_t r1; + uint32_t r2; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_ARM, UC_MODE_ARM, + (const char *)outer_code, sizeof(outer_code), + UC_CPU_ARM_CORTEX_A15); + OK(uc_mem_write(uc, nested_address, nested_code, sizeof(nested_code))); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, &memory_value, + sizeof(memory_value))); + OK(uc_reg_write(uc, UC_ARM_REG_R0, &r0)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, + test_arm_memory_hook_nested_flush_callback, &data, + data_address, + data_address + sizeof(memory_value) - 1)); + + OK(uc_emu_start(uc, code_start, + code_start + sizeof(outer_code), 0, 0)); + OK(uc_reg_read(uc, UC_ARM_REG_R1, &r1)); + OK(uc_reg_read(uc, UC_ARM_REG_R2, &r2)); + TEST_CHECK_(data.count == 1, "count=%u", data.count); + TEST_CHECK_(r1 == memory_value + 1, "r1=0x%x", r1); + TEST_CHECK(r2 == 7); - TEST_CHECK(counter[0] == 2 && counter[1] == 2); OK(uc_close(uc)); } @@ -11809,12 +13307,14 @@ typedef struct { static void _uc_hook_sub_cmp(uc_engine *uc, uint64_t address, uint64_t arg1, uint64_t arg2, uint32_t size, - _last_cmp_info *user_data) + void *user_data) { - user_data->pc = address; - user_data->size = size; - user_data->v0 = arg1; - user_data->v1 = arg2; + _last_cmp_info *cmp_info = user_data; + + cmp_info->pc = address; + cmp_info->size = size; + cmp_info->v0 = arg1; + cmp_info->v1 = arg2; } static void test_arm_tcg_opcode_cmp(void) @@ -11944,7 +13444,7 @@ static bool test_arm_v7_lpae_hook_tlb(uc_engine *uc, uint64_t addr, static void test_arm_v7_lpae_hook_read(uc_engine *uc, uc_mem_type type, uint64_t address, int size, - uint64_t value, void *user_data) + int64_t value, void *user_data) { TEST_CHECK(address == 0x100001000); } @@ -11975,7 +13475,8 @@ static void test_arm_v7_lpae(void) OK(uc_close(uc)); } -static void test_arm_svc_interrupt(uc_engine *uc, int intno, void *user_data) +static void test_arm_svc_interrupt(uc_engine *uc, uint32_t intno, + void *user_data) { uint32_t esr; OK(uc_reg_read(uc, UC_ARM_REG_ESR, &esr)); @@ -12038,11 +13539,25 @@ static void test_arm_hook_insn_wfi(void) } TEST_LIST = {{"test_arm_nop", test_arm_nop}, + {"test_arm_query_initial_thumb_mode", + test_arm_query_initial_thumb_mode}, + {"test_arm_legacy_count_transition", + test_arm_legacy_count_transition}, + {"test_arm_exclusive_monitor", test_arm_exclusive_monitor}, + {"test_arm_thumb2_exclusive_monitor", + test_arm_thumb2_exclusive_monitor}, {"test_arm_thumb_sub", test_arm_thumb_sub}, {"test_armeb_sub", test_armeb_sub}, {"test_armeb_be8_sub", test_armeb_be8_sub}, {"test_arm_thumbeb_sub", test_arm_thumbeb_sub}, {"test_arm_thumb_ite", test_arm_thumb_ite}, + {"test_arm_thumb_it_pending_stop", + test_arm_thumb_it_pending_stop}, + {"test_arm_fetch_predicate", test_arm_fetch_predicate}, + {"test_arm_thumb_fetch_predicate", + test_arm_thumb_fetch_predicate}, + {"test_arm_fetch_predicate_code_mutation", + test_arm_fetch_predicate_code_mutation}, {"test_arm_m_thumb_mrs", test_arm_m_thumb_mrs}, {"test_arm_i8mm", test_arm_i8mm}, {"test_arm_bf16", test_arm_bf16}, @@ -12053,10 +13568,13 @@ TEST_LIST = {{"test_arm_nop", test_arm_nop}, {"test_arm_m55_fpscr_ltpsize", test_arm_m55_fpscr_ltpsize}, {"test_arm_m55_fpscr_nzcvqc_sysreg", test_arm_m55_fpscr_nzcvqc_sysreg}, + {"test_arm_m55_vlldm", test_arm_m55_vlldm}, {"test_arm_m55_vlstm_lazy_preserve", test_arm_m55_vlstm_lazy_preserve}, {"test_arm_m55_vlstm_lazy_fault", test_arm_m55_vlstm_lazy_fault}, + {"test_arm_m55_fpcxt_s", test_arm_m55_fpcxt_s}, + {"test_arm_m55_fpcxt_ns", test_arm_m55_fpcxt_ns}, {"test_arm_m55_sysreg_mem", test_arm_m55_sysreg_mem}, {"test_arm_m55_vscclrm", test_arm_m55_vscclrm}, {"test_arm_m55_vctp", test_arm_m55_vctp}, @@ -12134,9 +13652,31 @@ TEST_LIST = {{"test_arm_nop", test_arm_nop}, {"test_arm_switch_endian", test_arm_switch_endian}, {"test_armeb_ldrb", test_armeb_ldrb}, {"test_arm_context_save", test_arm_context_save}, + {"test_arm_context_cross_engine_after_source_close", + test_arm_context_cross_engine_after_source_close}, + {"test_arm_context_serialized_restore", + test_arm_context_serialized_restore}, + {"test_arm_context_incompatible_mode", + test_arm_context_incompatible_mode}, + {"test_arm_context_cross_model_size", + test_arm_context_cross_model_size}, + {"test_arm_memory_context_cross_owner_save_unchanged", + test_arm_memory_context_cross_owner_save_unchanged}, + {"test_arm_context_cross_model_features", + test_arm_context_cross_model_features}, + {"test_arm_context_source_close_debug_state", + test_arm_context_source_close_debug_state}, + {"test_arm_context_cross_model_debug_gating", + test_arm_context_cross_model_debug_gating}, + {"test_arm_context_malformed_cpu_memory_atomic", + test_arm_context_malformed_cpu_memory_atomic}, + {"test_arm_context_raw_buffer", + test_arm_context_raw_buffer}, {"test_arm_thumb2", test_arm_thumb2}, {"test_armeb_be32_thumb2", test_armeb_be32_thumb2}, {"test_arm_mem_hook_read_write", test_arm_mem_hook_read_write}, + {"test_arm_memory_hook_nested_tb_flush", + test_arm_memory_hook_nested_tb_flush}, {"test_arm_thumb_it_mem_hook", test_arm_thumb_it_mem_hook}, {"test_arm_tcg_opcode_cmp", test_arm_tcg_opcode_cmp}, {"test_arm_thumb_tcg_opcode_cmn", test_arm_thumb_tcg_opcode_cmn}, diff --git a/tests/unit/test_arm64.c b/tests/unit/test_arm64.c index dd743daad1..36831b8fcd 100644 --- a/tests/unit/test_arm64.c +++ b/tests/unit/test_arm64.c @@ -123,6 +123,153 @@ static void test_arm64_code_patching_count(void) OK(uc_close(uc)); } +typedef struct Arm64CallbackSmc { + uint64_t patch_address; + uint32_t patch; + uint32_t entry_count; + uc_err write_error; + bool patched; +} Arm64CallbackSmc; + +static void test_arm64_callback_smc_hook(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + Arm64CallbackSmc *smc = (Arm64CallbackSmc *)user_data; + + (void)size; + if (address != code_start) { + return; + } + + smc->entry_count++; + if (!smc->patched) { + smc->patched = true; + smc->write_error = uc_mem_write(uc, smc->patch_address, &smc->patch, + sizeof(smc->patch)); + } +} + +static void test_arm64_callback_active_tb_smc(void) +{ + const uint32_t code[] = { + LEINT32(0xd503201f), /* nop */ + LEINT32(0x52800021), /* mov w1,#1 */ + LEINT32(0x52800062), /* mov w2,#3 */ + }; + Arm64CallbackSmc smc = { + .patch_address = code_start + 4, + .patch = LEINT32(0x528000e1), /* mov w1,#7 */ + .write_error = UC_ERR_OK, + }; + uc_engine *uc; + uc_hook hook; + uint64_t x1 = 0; + uint64_t x2 = 0; + + uc_common_setup(&uc, UC_ARCH_ARM64, UC_MODE_ARM, (const char *)code, + sizeof(code), UC_CPU_ARM64_A72); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, test_arm64_callback_smc_hook, &smc, + 1, 0)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(smc.write_error); + OK(uc_reg_read(uc, UC_ARM64_REG_X1, &x1)); + OK(uc_reg_read(uc, UC_ARM64_REG_X2, &x2)); + TEST_CHECK(smc.patched); + TEST_CHECK_(smc.entry_count == 2, "entry_count=%u", smc.entry_count); + TEST_CHECK_(x1 == 7, "x1=0x%llx", (unsigned long long)x1); + TEST_CHECK_(x2 == 3, "x2=0x%llx", (unsigned long long)x2); + + OK(uc_close(uc)); +} + +static void test_arm64_guest_store_cached_tb_smc(void) +{ + const uint32_t target_code[] = { + LEINT32(0x52800022), /* mov w2,#1 */ + LEINT32(0x52800063), /* mov w3,#3 */ + }; + const uint32_t writer_code[] = { + LEINT32(0xb9000001), /* str w1,[x0] */ + }; + const uint32_t patch = LEINT32(0x52800102); /* mov w2,#8 */ + const uint64_t writer_address = code_start + 0x100; + uc_engine *uc; + uint64_t x0 = code_start; + uint64_t x1 = 0x52800102; + uint64_t x2 = 0; + uint64_t x3 = 0; + uint32_t stored = 0; + + uc_common_setup(&uc, UC_ARCH_ARM64, UC_MODE_ARM, (const char *)target_code, + sizeof(target_code), UC_CPU_ARM64_A72); + OK(uc_mem_write(uc, writer_address, writer_code, sizeof(writer_code))); + OK(uc_emu_start(uc, code_start, code_start + sizeof(target_code), 0, 0)); + OK(uc_reg_read(uc, UC_ARM64_REG_X2, &x2)); + TEST_CHECK_(x2 == 1, "initial x2=0x%llx", (unsigned long long)x2); + + x2 = 0; + OK(uc_reg_write(uc, UC_ARM64_REG_X2, &x2)); + OK(uc_reg_write(uc, UC_ARM64_REG_X0, &x0)); + OK(uc_reg_write(uc, UC_ARM64_REG_X1, &x1)); + + OK(uc_emu_start(uc, writer_address, writer_address + sizeof(writer_code), 0, + 0)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(target_code), 0, 0)); + + OK(uc_mem_read(uc, code_start, &stored, sizeof(stored))); + OK(uc_reg_read(uc, UC_ARM64_REG_X2, &x2)); + OK(uc_reg_read(uc, UC_ARM64_REG_X3, &x3)); + TEST_CHECK(stored == patch); + TEST_CHECK_(x2 == 8, "x2=0x%llx", (unsigned long long)x2); + TEST_CHECK_(x3 == 3, "x3=0x%llx", (unsigned long long)x3); + + OK(uc_close(uc)); +} + +static void test_arm64_guest_smc_cache_maintenance(void) +{ + const uint32_t code[] = { + LEINT32(0xb9000001), /* str w1,[x0] */ + LEINT32(0xd50b7b20), /* dc cvau,x0 */ + LEINT32(0xd5033b9f), /* dsb ish */ + LEINT32(0xd50b7520), /* ic ivau,x0 */ + LEINT32(0xd5033b9f), /* dsb ish */ + LEINT32(0xd5033fdf), /* isb */ + LEINT32(0x52800022), /* mov w2,#1 */ + LEINT32(0x52800063), /* mov w3,#3 */ + }; + const uint32_t patch = LEINT32(0x52800102); /* mov w2,#8 */ + const uint64_t patch_address = code_start + 6 * sizeof(uint32_t); + const uint64_t code_end = code_start + sizeof(code); + uc_engine *uc; + uint64_t x0 = patch_address; + uint64_t x1 = 0x52800102; + uint64_t x2 = 0; + uint64_t x3 = 0; + uint64_t pc = 0; + uint32_t stored = 0; + + uc_common_setup(&uc, UC_ARCH_ARM64, UC_MODE_ARM, (const char *)code, + sizeof(code), UC_CPU_ARM64_A72); + OK(uc_reg_write(uc, UC_ARM64_REG_X0, &x0)); + OK(uc_reg_write(uc, UC_ARM64_REG_X1, &x1)); + + OK(uc_emu_start(uc, code_start, code_end, 0, 0)); + + OK(uc_mem_read(uc, patch_address, &stored, sizeof(stored))); + OK(uc_reg_read(uc, UC_ARM64_REG_X2, &x2)); + OK(uc_reg_read(uc, UC_ARM64_REG_X3, &x3)); + OK(uc_reg_read(uc, UC_ARM64_REG_PC, &pc)); + TEST_CHECK(stored == patch); + TEST_CHECK_(x2 == 8, "x2=0x%llx", (unsigned long long)x2); + TEST_CHECK_(x3 == 3, "x3=0x%llx", (unsigned long long)x3); + TEST_CHECK_(pc == code_end, "pc=0x%llx", (unsigned long long)pc); + + OK(uc_close(uc)); +} + static void test_arm64_v8_cas(void) { uc_engine *uc; @@ -232,8 +379,8 @@ static void test_arm64_lse_signed_minmax_byte(void) OK(uc_reg_read(uc, UC_ARM64_REG_X2, &x2)); OK(uc_reg_read(uc, UC_ARM64_REG_X4, &x4)); OK(uc_mem_read(uc, 0x40000, &data, sizeof(data))); - TEST_CHECK_(x2 == 0x80, "x2=0x%llx", x2); - TEST_CHECK_(x4 == 0x7f, "x4=0x%llx", x4); + TEST_CHECK_(x2 == 0x80, "x2=0x%llx", (unsigned long long)x2); + TEST_CHECK_(x4 == 0x7f, "x4=0x%llx", (unsigned long long)x4); TEST_CHECK_(data == 0x80, "data=0x%x", data); OK(uc_close(uc)); } @@ -283,11 +430,15 @@ static void test_arm64_read_sctlr(void) OK(uc_close(uc)); } +static uint32_t test_arm64_hook_insn_mrs_count; + static uint32_t test_arm64_hook_insn_mrs_cb(uc_engine *uc, uc_arm64_reg reg, - const uc_arm64_cp_reg *cp_reg) + const uc_arm64_cp_reg *cp_reg, + void *user_data) { uint64_t r_x2 = 0x114514; + test_arm64_hook_insn_mrs_count++; OK(uc_reg_write(uc, reg, &r_x2)); // Skip @@ -305,6 +456,10 @@ static void test_arm64_hook_insn_mrs(void) uc_common_setup(&uc, UC_ARCH_ARM64, UC_MODE_LITTLE_ENDIAN | UC_MODE_ARM, code, sizeof(code) - 1, UC_CPU_ARM64_A72); + test_arm64_hook_insn_mrs_count = 0; + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); + TEST_CHECK(test_arm64_hook_insn_mrs_count == 0); + OK(uc_hook_add(uc, &hk, UC_HOOK_INSN, (void *)test_arm64_hook_insn_mrs_cb, NULL, 1, 0, UC_ARM64_INS_MRS)); @@ -313,9 +468,14 @@ static void test_arm64_hook_insn_mrs(void) OK(uc_reg_read(uc, UC_ARM64_REG_X2, &r_x2)); TEST_CHECK(r_x2 == 0x114514); + TEST_CHECK(test_arm64_hook_insn_mrs_count == 1); OK(uc_hook_del(uc, hk)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); + TEST_CHECK(test_arm64_hook_insn_mrs_count == 1); + OK(uc_close(uc)); } @@ -326,28 +486,48 @@ static int test_arm64_hook_insn_wfi_callback(uc_engine *uc, void *user_data) return 0; } +static uint32_t test_arm64_hook_insn_mrs_unrelated_callback( + uc_engine *uc, uc_arm64_reg reg, const uc_arm64_cp_reg *cp_reg, + void *user_data) +{ + WFI_HOOK_INSN_RESULT *result = (WFI_HOOK_INSN_RESULT *)user_data; + + (void)uc; + (void)reg; + (void)cp_reg; + result->called = true; + return 0; +} + static void test_arm64_hook_insn_wfi(void) { uc_engine *uc; uc_hook hook; + uc_hook unrelated_hook; char code[] = "\x7f\x20\x03\xd5"; // wfi WFI_HOOK_INSN_RESULT result = {false}; + WFI_HOOK_INSN_RESULT unrelated = {false}; uc_common_setup(&uc, UC_ARCH_ARM64, UC_MODE_LITTLE_ENDIAN | UC_MODE_ARM, code, sizeof(code) - 1, UC_CPU_ARM64_A72); OK(uc_hook_add(uc, &hook, UC_HOOK_INSN, test_arm64_hook_insn_wfi_callback, &result, 1, 0, UC_ARM64_INS_WFI)); + OK(uc_hook_add(uc, &unrelated_hook, UC_HOOK_INSN, + test_arm64_hook_insn_mrs_unrelated_callback, &unrelated, + 1, 0, UC_ARM64_INS_MRS)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); TEST_CHECK(result.called == true); + TEST_CHECK(unrelated.called == false); OK(uc_hook_del(uc, hook)); + OK(uc_hook_del(uc, unrelated_hook)); OK(uc_close(uc)); } static bool test_arm64_correct_address_in_small_jump_hook_callback( - uc_engine *uc, int type, uint64_t address, int size, int64_t value, - void *user_data) + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) { // Check registers uint64_t r_x0 = 0x0; @@ -394,8 +574,8 @@ static void test_arm64_correct_address_in_small_jump_hook(void) } static bool test_arm64_correct_address_in_long_jump_hook_callback( - uc_engine *uc, int type, uint64_t address, int size, int64_t value, - void *user_data) + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) { // Check registers uint64_t r_x0 = 0x0; @@ -481,12 +661,10 @@ static void test_arm64_block_sync_pc(void) OK(uc_close(uc)); } -static bool -test_arm64_block_invalid_mem_read_write_sync_cb(uc_engine *uc, int type, - uint64_t address, int size, - int64_t value, void *user_data) +static void test_arm64_block_invalid_mem_read_write_sync_cb( + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) { - return 0; } static void test_arm64_block_invalid_mem_read_write_sync(void) @@ -744,9 +922,9 @@ static void test_arm64_mem_prot_regress(void) OK(uc_close(uc)); } -static bool test_arm64_mem_read_write_cb(uc_engine *uc, int type, - uint64_t address, int size, - int64_t value, void *user_data) +static void test_arm64_mem_read_write_cb(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) { uint64_t *count = (uint64_t *)user_data; switch (type) { @@ -758,7 +936,6 @@ static bool test_arm64_mem_read_write_cb(uc_engine *uc, int type, break; } - return 0; } static void test_arm64_mem_hook_read_write(void) { @@ -871,6 +1048,87 @@ static uint32_t test_arm64_msr_sysreg(uint32_t rt, (cpregid[4] << 5) | rt; } +static void test_arm64_eret_el1_to_el0(void) +{ + const char eret[] = "\xe0\x03\x9f\xd6"; + const char target_code[] = + "\x40\x05\x80\xd2" /* mov x0, #42 */ + "\xe1\x03\x00\x91"; /* mov x1, sp */ + const uint32_t ELR_EL1[5] = { 3, 0, 4, 0, 1 }; + const uint32_t SPSR_EL1[5] = { 3, 0, 4, 0, 0 }; + const uint64_t target = code_start + 0x1000; + const uint64_t sp_el1 = 0x41000; + uint32_t pstate = 5; + uint64_t pc = code_start; + uint64_t sp = sp_el1; + uint64_t sp_el0 = 0x42000; + uint64_t x0 = 0; + uint64_t x1 = 0; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_ARM64, UC_MODE_ARM, eret, + sizeof(eret) - 1, UC_CPU_ARM64_A72); + OK(uc_mem_write(uc, target, target_code, sizeof(target_code) - 1)); + OK(uc_reg_write(uc, UC_ARM64_REG_PSTATE, &pstate)); + OK(uc_reg_write(uc, UC_ARM64_REG_SP_EL0, &sp_el0)); + OK(uc_reg_write(uc, UC_ARM64_REG_SP, &sp)); + test_arm64_pauth_cp_reg_write(uc, ELR_EL1, target); + test_arm64_pauth_cp_reg_write(uc, SPSR_EL1, 0); + OK(uc_reg_write(uc, UC_ARM64_REG_PC, &pc)); + + OK(uc_emu_start(uc, code_start, UINT64_MAX, 0, 3)); + OK(uc_reg_read(uc, UC_ARM64_REG_PSTATE, &pstate)); + OK(uc_reg_read(uc, UC_ARM64_REG_PC, &pc)); + OK(uc_reg_read(uc, UC_ARM64_REG_SP, &sp)); + OK(uc_reg_read(uc, UC_ARM64_REG_X0, &x0)); + OK(uc_reg_read(uc, UC_ARM64_REG_X1, &x1)); + + TEST_CHECK((pstate & 0xf) == 0); + TEST_CHECK((pstate & (1U << 20)) == 0); + TEST_CHECK(pc == target + sizeof(target_code) - 1); + TEST_CHECK(sp == sp_el0); + TEST_CHECK(x0 == 42); + TEST_CHECK(x1 == sp_el0); + OK(uc_close(uc)); +} + +static void test_arm64_eret_illegal_spsr(void) +{ + const char eret[] = "\xe0\x03\x9f\xd6"; + const char target_code[] = "\x40\x05\x80\xd2"; /* mov x0, #42 */ + const uint32_t ELR_EL1[5] = { 3, 0, 4, 0, 1 }; + const uint32_t SPSR_EL1[5] = { 3, 0, 4, 0, 0 }; + const uint64_t target = code_start + 0x1000; + const uint64_t sp_el1 = 0x41000; + uint32_t pstate = 5; + uint64_t pc = code_start; + uint64_t sp = sp_el1; + uint64_t x0 = 0; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_ARM64, UC_MODE_ARM, eret, + sizeof(eret) - 1, UC_CPU_ARM64_A72); + OK(uc_mem_write(uc, target, target_code, sizeof(target_code) - 1)); + OK(uc_reg_write(uc, UC_ARM64_REG_PSTATE, &pstate)); + OK(uc_reg_write(uc, UC_ARM64_REG_SP, &sp)); + test_arm64_pauth_cp_reg_write(uc, ELR_EL1, target); + test_arm64_pauth_cp_reg_write(uc, SPSR_EL1, 2); + OK(uc_reg_write(uc, UC_ARM64_REG_PC, &pc)); + + OK(uc_emu_start(uc, code_start, UINT64_MAX, 0, 2)); + OK(uc_reg_read(uc, UC_ARM64_REG_PSTATE, &pstate)); + OK(uc_reg_read(uc, UC_ARM64_REG_PC, &pc)); + OK(uc_reg_read(uc, UC_ARM64_REG_SP, &sp)); + OK(uc_reg_read(uc, UC_ARM64_REG_X0, &x0)); + + TEST_CHECK((pstate & 0xf) == 5); + TEST_CHECK((pstate & (1U << 20)) != 0); + TEST_CHECK(pc == target + sizeof(target_code) - 1); + TEST_CHECK(sp == sp_el1); + TEST_CHECK(x0 == 42); + OK(uc_close(uc)); +} + static void test_arm64_pauth_check_cpu_feat(uc_engine *uc) { // Check the CPU actually supports any form of PAuth, i.e. any APA or API @@ -3496,6 +3754,118 @@ static void test_arm64_emit32(uint8_t *code, int offset, uint32_t insn) code[offset + 3] = (uint8_t)(insn >> 24); } +static void test_arm64_advsimd_aes_sha256(void) +{ + const uint32_t ID_AA64ISAR0_EL1[5] = { 3, 0, 0, 6, 0 }; + const uint8_t aes_state[16] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + }; + const uint8_t aes_key[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + }; + const uint8_t aes_expected[16] = { + 0x5f, 0x72, 0x64, 0x15, 0x57, 0xf5, 0xbc, 0x92, + 0xf7, 0xbe, 0x3b, 0x29, 0x1d, 0xb9, 0xf9, 0x1a, + }; + const uint32_t sha_abcd[4] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + }; + const uint32_t sha_efgh[4] = { + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + }; + const uint32_t sha_wk[4] = { + 0xc28a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + }; + const uint32_t sha_expected[4] = { + 0x4c23eacb, 0xc4ac5526, 0x9ce564d0, 0x7c08884d, + }; + uint8_t code[12]; + uint64_t q0[2]; + uint64_t q1[2]; + uint64_t q2[2]; + uint64_t q3[2]; + uint64_t q4[2]; + uint64_t isar0; + uc_engine *uc; + + test_arm64_emit32(code, 0, 0x4e284820); /* aese v0.16b,v1.16b */ + test_arm64_emit32(code, 4, 0x4e286800); /* aesmc v0.16b,v0.16b */ + test_arm64_emit32(code, 8, 0x5e044062); /* sha256h q2,q3,v4.4s */ + memcpy(q0, aes_state, sizeof(q0)); + memcpy(q1, aes_key, sizeof(q1)); + memcpy(q2, sha_abcd, sizeof(q2)); + memcpy(q3, sha_efgh, sizeof(q3)); + memcpy(q4, sha_wk, sizeof(q4)); + + uc_common_setup(&uc, UC_ARCH_ARM64, UC_MODE_ARM, (const char *)code, + sizeof(code), UC_CPU_ARM64_A72); + isar0 = test_arm64_pauth_cp_reg_read(uc, ID_AA64ISAR0_EL1); + TEST_CHECK(((isar0 >> 4) & 0xf) == 2); + TEST_CHECK(((isar0 >> 12) & 0xf) == 1); + OK(uc_reg_write(uc, UC_ARM64_REG_Q0, q0)); + OK(uc_reg_write(uc, UC_ARM64_REG_Q1, q1)); + OK(uc_reg_write(uc, UC_ARM64_REG_Q2, q2)); + OK(uc_reg_write(uc, UC_ARM64_REG_Q3, q3)); + OK(uc_reg_write(uc, UC_ARM64_REG_Q4, q4)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_ARM64_REG_Q0, q0)); + OK(uc_reg_read(uc, UC_ARM64_REG_Q2, q2)); + TEST_CHECK(memcmp(q0, aes_expected, sizeof(q0)) == 0); + TEST_CHECK(memcmp(q2, sha_expected, sizeof(q2)) == 0); + OK(uc_close(uc)); +} + +static void test_arm64_advsimd_sha512_gating(void) +{ + const uint32_t ID_AA64ISAR0_EL1[5] = { 3, 0, 0, 6, 0 }; + const uint64_t initial[2] = { + 0x0123456789abcdefull, 0xfedcba9876543210ull, + }; + const uint64_t n[2] = { + 0x0f1e2d3c4b5a6978ull, 0x8877665544332211ull, + }; + const uint64_t m[2] = { + 0x1122334455667788ull, 0x99aabbccddeeff00ull, + }; + const uint64_t expected[2] = { + 0xf22187af14bd61d2ull, 0xade8df75813c30beull, + }; + uint8_t code[4]; + uint64_t q0[2]; + uint64_t isar0; + uc_engine *uc; + + test_arm64_emit32(code, 0, 0xce628020); /* sha512h q0,q1,v2.2d */ + memcpy(q0, initial, sizeof(q0)); + uc_common_setup(&uc, UC_ARCH_ARM64, UC_MODE_ARM, (const char *)code, + sizeof(code), UC_CPU_ARM64_MAX); + isar0 = test_arm64_pauth_cp_reg_read(uc, ID_AA64ISAR0_EL1); + TEST_CHECK(((isar0 >> 12) & 0xf) == 2); + OK(uc_reg_write(uc, UC_ARM64_REG_Q0, q0)); + OK(uc_reg_write(uc, UC_ARM64_REG_Q1, n)); + OK(uc_reg_write(uc, UC_ARM64_REG_Q2, m)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_ARM64_REG_Q0, q0)); + TEST_CHECK(memcmp(q0, expected, sizeof(q0)) == 0); + OK(uc_close(uc)); + + memcpy(q0, initial, sizeof(q0)); + uc_common_setup(&uc, UC_ARCH_ARM64, UC_MODE_ARM, (const char *)code, + sizeof(code), UC_CPU_ARM64_A72); + isar0 = test_arm64_pauth_cp_reg_read(uc, ID_AA64ISAR0_EL1); + TEST_CHECK(((isar0 >> 12) & 0xf) == 1); + OK(uc_reg_write(uc, UC_ARM64_REG_Q0, q0)); + OK(uc_reg_write(uc, UC_ARM64_REG_Q1, n)); + OK(uc_reg_write(uc, UC_ARM64_REG_Q2, m)); + TEST_CHECK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0) == + UC_ERR_EXCEPTION); + OK(uc_reg_read(uc, UC_ARM64_REG_Q0, q0)); + TEST_CHECK(memcmp(q0, initial, sizeof(q0)) == 0); + OK(uc_close(uc)); +} + static void test_arm64_mte_simd_fp_single_access(void) { const uint32_t TFSR_EL1[5] = { 3, 0, 5, 6, 0 }; @@ -6775,6 +7145,112 @@ static void test_arm64_sme_context_save_restore(void) OK(uc_close(uc)); } +typedef struct Arm64IntrCapture { + uint32_t count; + uint32_t intno; +} Arm64IntrCapture; + +static void test_arm64_context_debug_intr_cb(uc_engine *uc, uint32_t intno, + void *user_data) +{ + Arm64IntrCapture *capture = (Arm64IntrCapture *)user_data; + + capture->count++; + capture->intno = intno; + uc_emu_stop(uc); +} + +static void test_arm64_context_check_debug_state(uc_engine *uc, + Arm64IntrCapture *capture) +{ + uc_hook hook; + + OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, + test_arm64_context_debug_intr_cb, capture, 1, 0)); + + OK(uc_emu_start(uc, code_start + 20, code_start + 24, 0, 1)); + TEST_CHECK_(capture->count == 1, + "breakpoint count=%u intno=%u", capture->count, + capture->intno); + + capture->count = 0; + capture->intno = 0; + OK(uc_emu_start(uc, code_start + 24, code_start + 28, 0, 1)); + TEST_CHECK_(capture->count == 1, + "watchpoint count=%u intno=%u", capture->count, + capture->intno); + OK(uc_hook_del(uc, hook)); +} + +static void test_arm64_context_debug_lifecycle(void) +{ + const uint32_t dbgbvr0_el1[5] = {2, 0, 0, 0, 4}; + const uint32_t dbgbcr0_el1[5] = {2, 0, 0, 0, 5}; + const uint32_t dbgwvr0_el1[5] = {2, 0, 0, 0, 6}; + const uint32_t dbgwcr0_el1[5] = {2, 0, 0, 0, 7}; + const uint32_t mdscr_el1[5] = {2, 0, 0, 2, 2}; + const uint64_t breakpoint_address = code_start + 20; + const uint64_t watchpoint_address = code_start + 0x100; + const uint64_t breakpoint_control = 1U | (3U << 1) | (0xfU << 5); + const uint64_t watchpoint_control = + 1U | (3U << 1) | (2U << 3) | (0xfU << 5); + const uint64_t mdscr = (1U << 15) | (1U << 13); + const uint64_t stored_value = 0x12345678; + const uint32_t pstate_el1h = 5; + Arm64IntrCapture capture = {0}; + uc_engine *source; + uc_engine *destination; + uc_context *context; + uint8_t code[28]; + uint64_t disabled = 0; + + test_arm64_emit32(code, 0, + test_arm64_msr_sysreg(0, dbgbvr0_el1)); + test_arm64_emit32(code, 4, + test_arm64_msr_sysreg(1, dbgbcr0_el1)); + test_arm64_emit32(code, 8, + test_arm64_msr_sysreg(2, dbgwvr0_el1)); + test_arm64_emit32(code, 12, + test_arm64_msr_sysreg(3, dbgwcr0_el1)); + test_arm64_emit32(code, 16, + test_arm64_msr_sysreg(4, mdscr_el1)); + test_arm64_emit32(code, 20, 0xd503201f); /* nop */ + test_arm64_emit32(code, 24, 0xb9000046); /* str w6, [x2] */ + + uc_common_setup(&source, UC_ARCH_ARM64, UC_MODE_ARM, + (const char *)code, sizeof(code), UC_CPU_ARM64_MAX); + uc_common_setup(&destination, UC_ARCH_ARM64, UC_MODE_ARM, + (const char *)code, sizeof(code), UC_CPU_ARM64_MAX); + OK(uc_reg_write(source, UC_ARM64_REG_PSTATE, &pstate_el1h)); + OK(uc_reg_write(source, UC_ARM64_REG_X0, &breakpoint_address)); + OK(uc_reg_write(source, UC_ARM64_REG_X1, &breakpoint_control)); + OK(uc_reg_write(source, UC_ARM64_REG_X2, &watchpoint_address)); + OK(uc_reg_write(source, UC_ARM64_REG_X3, &watchpoint_control)); + OK(uc_reg_write(source, UC_ARM64_REG_X4, &mdscr)); + OK(uc_reg_write(source, UC_ARM64_REG_X6, &stored_value)); + OK(uc_emu_start(source, code_start, breakpoint_address, 0, 5)); + test_arm64_context_check_debug_state(source, &capture); + capture = (Arm64IntrCapture){0}; + + OK(uc_context_alloc(source, &context)); + OK(uc_context_save(source, context)); + + OK(uc_reg_write(source, UC_ARM64_REG_X1, &disabled)); + OK(uc_emu_start(source, code_start + 4, code_start + 8, 0, 1)); + OK(uc_reg_write(source, UC_ARM64_REG_X3, &disabled)); + OK(uc_emu_start(source, code_start + 12, code_start + 16, 0, 1)); + OK(uc_context_restore(source, context)); + test_arm64_context_check_debug_state(source, &capture); + + capture = (Arm64IntrCapture){0}; + OK(uc_close(source)); + OK(uc_context_restore(destination, context)); + test_arm64_context_check_debug_state(destination, &capture); + + OK(uc_context_free(context)); + OK(uc_close(destination)); +} + static void test_arm64_sme_mova_q_horizontal(void) { const uint32_t SMCR_EL1[5] = { 3, 0, 1, 2, 6 }; @@ -11894,7 +12370,7 @@ static void test_arm64_mte_dc_zva_original_fault_addr(void) 0, 0) == UC_ERR_WRITE_UNMAPPED); OK(uc_ctl_get_invalid_addr(uc, &invalid_addr)); TEST_CHECK_(invalid_addr == 0x40020, "invalid_addr=0x%llx", - invalid_addr); + (unsigned long long)invalid_addr); OK(uc_close(uc)); } @@ -12406,199 +12882,183 @@ static void test_arm64_mte_requires_max(void) OK(uc_close(uc)); } -TEST_LIST = {{"test_arm64_until", test_arm64_until}, - {"test_arm64_code_patching", test_arm64_code_patching}, - {"test_arm64_code_patching_count", test_arm64_code_patching_count}, - {"test_arm64_v8_cas", test_arm64_v8_cas}, - {"test_arm64_lse_rcpc_unaligned", - test_arm64_lse_rcpc_unaligned}, - {"test_arm64_lse_signed_minmax_byte", - test_arm64_lse_signed_minmax_byte}, - {"test_arm64_lse_rcpc_id_registers", - test_arm64_lse_rcpc_id_registers}, - {"test_arm64_lse_rcpc_a72_rejects", - test_arm64_lse_rcpc_a72_rejects}, - {"test_arm64_dgh_hint", test_arm64_dgh_hint}, - {"test_arm64_read_sctlr", test_arm64_read_sctlr}, - {"test_arm64_hook_insn_mrs", test_arm64_hook_insn_mrs}, - {"test_arm64_hook_insn_wfi", test_arm64_hook_insn_wfi}, - {"test_arm64_correct_address_in_small_jump_hook", - test_arm64_correct_address_in_small_jump_hook}, - {"test_arm64_correct_address_in_long_jump_hook", - test_arm64_correct_address_in_long_jump_hook}, - {"test_arm64_block_sync_pc", test_arm64_block_sync_pc}, - {"test_arm64_block_invalid_mem_read_write_sync", - test_arm64_block_invalid_mem_read_write_sync}, - {"test_arm64_mmu", test_arm64_mmu}, - {"test_arm64_pc_wrap", test_arm64_pc_wrap}, - {"test_arm64_mem_prot_regress", test_arm64_mem_prot_regress}, - {"test_arm64_mem_hook_read_write", test_arm64_mem_hook_read_write}, - {"test_arm64_pc_guarantee", test_arm64_pc_guarantee}, - {"test_arm64_sve_id_registers", test_arm64_sve_id_registers}, - {"test_arm64_sme_foundation", test_arm64_sme_foundation}, - {"test_arm64_sme_svlength", test_arm64_sme_svlength}, - {"test_arm64_sme_nonstreaming_sve_ffr", - test_arm64_sme_nonstreaming_sve_ffr}, - {"test_arm64_sme_nonstreaming_sve_misc", - test_arm64_sme_nonstreaming_sve_misc}, - {"test_arm64_sme_zero_mova", test_arm64_sme_zero_mova}, - {"test_arm64_sme_context_save_restore", - test_arm64_sme_context_save_restore}, - {"test_arm64_sme_mova_q_horizontal", - test_arm64_sme_mova_q_horizontal}, - {"test_arm64_sme_adda", test_arm64_sme_adda}, - {"test_arm64_sme_ldstr", test_arm64_sme_ldstr}, - {"test_arm64_sme_ldst1", test_arm64_sme_ldst1}, - {"test_arm64_sme_ldst1_fault_no_partial", - test_arm64_sme_ldst1_fault_no_partial}, - {"test_arm64_sme_psel", test_arm64_sme_psel}, - {"test_arm64_sme_ldst1_mte", test_arm64_sme_ldst1_mte}, - {"test_arm64_sme_imopa", test_arm64_sme_imopa}, - {"test_arm64_sme_fpout", test_arm64_sme_fpout}, - {"test_arm64_i8mm_advsimd", test_arm64_i8mm_advsimd}, - {"test_arm64_bf16_advsimd", test_arm64_bf16_advsimd}, - {"test_arm64_pauth_vanilla", test_arm64_pauth_vanilla}, - {"test_arm64_pauth_ctl", test_arm64_pauth_ctl}, - {"test_arm64_mte_register_only", test_arm64_mte_register_only}, - {"test_arm64_mte_ata_tag_generation", - test_arm64_mte_ata_tag_generation}, - {"test_arm64_mte_tag_load_store", - test_arm64_mte_tag_load_store}, - {"test_arm64_mte_tag_snapshot", test_arm64_mte_tag_snapshot}, - {"test_arm64_mte_tag_multiple", test_arm64_mte_tag_multiple}, - {"test_arm64_mte_checked_scalar_access", - test_arm64_mte_checked_scalar_access}, - {"test_arm64_mte_tco_msr_imm", - test_arm64_mte_tco_msr_imm}, - {"test_arm64_mte_simd_fp_single_access", - test_arm64_mte_simd_fp_single_access}, - {"test_arm64_mte_advsimd_struct_range", - test_arm64_mte_advsimd_struct_range}, - {"test_arm64_mte_lse_atomic_asym_sync_no_side_effect", - test_arm64_mte_lse_atomic_asym_sync_no_side_effect}, - {"test_arm64_mte_ldapr_sync_tag_check", - test_arm64_mte_ldapr_sync_tag_check}, - {"test_arm64_mte_lse_cas_asym_async_side_effect", - test_arm64_mte_lse_cas_asym_async_side_effect}, - {"test_arm64_mte_exclusive_asym_access", - test_arm64_mte_exclusive_asym_access}, - {"test_arm64_mte_sp_addressing_tagchecked", - test_arm64_mte_sp_addressing_tagchecked}, - {"test_arm64_mte_sp_writeback_tagchecked", - test_arm64_mte_sp_writeback_tagchecked}, - {"test_arm64_mte_pair_sp_tagchecked", - test_arm64_mte_pair_sp_tagchecked}, - {"test_arm64_mte_pac_load_sp_tagchecked", - test_arm64_mte_pac_load_sp_tagchecked}, - {"test_arm64_mte_tcma0_tag_zero_unchecked", - test_arm64_mte_tcma0_tag_zero_unchecked}, - {"test_arm64_mte_ldapur_stlur_unchecked", - test_arm64_mte_ldapur_stlur_unchecked}, - {"test_arm64_mte_ldapur_stlur_variants_unchecked", - test_arm64_mte_ldapur_stlur_variants_unchecked}, - {"test_arm64_mte_unpriv_sp_no_tag_check", - test_arm64_mte_unpriv_sp_no_tag_check}, - {"test_arm64_mte_unpriv_async_tag_check", - test_arm64_mte_unpriv_async_tag_check}, - {"test_arm64_mte_page_attrs", test_arm64_mte_page_attrs}, - {"test_arm64_bti_guarded_page", test_arm64_bti_guarded_page}, - {"test_arm64_mte_hcr_dct", test_arm64_mte_hcr_dct}, - {"test_arm64_mte_cross_page_fault_priority", - test_arm64_mte_cross_page_fault_priority}, - {"test_arm64_mte_ata_disabled_tag_op_probe", - test_arm64_mte_ata_disabled_tag_op_probe}, - {"test_arm64_sve2_non_temporal_gather_scatter", - test_arm64_sve2_non_temporal_gather_scatter}, - {"test_arm64_sve2_bitwise_ternary", - test_arm64_sve2_bitwise_ternary}, - {"test_arm64_sve2_xar", test_arm64_sve2_xar}, - {"test_arm64_sve2_pmull", test_arm64_sve2_pmull}, - {"test_arm64_sve2_mul_base", test_arm64_sve2_mul_base}, - {"test_arm64_sve2_mul_indexed", test_arm64_sve2_mul_indexed}, - {"test_arm64_sve2_widen_indexed", - test_arm64_sve2_widen_indexed}, - {"test_arm64_sve2_widen_accumulate", - test_arm64_sve2_widen_accumulate}, - {"test_arm64_sve2_abs_accumulate", - test_arm64_sve2_abs_accumulate}, - {"test_arm64_sve2_cadd_sqcadd", - test_arm64_sve2_cadd_sqcadd}, - {"test_arm64_sve2_sqrdmla", test_arm64_sve2_sqrdmla}, - {"test_arm64_sve2_complex_dot", - test_arm64_sve2_complex_dot}, - {"test_arm64_sve_i8mm", test_arm64_sve_i8mm}, - {"test_arm64_sve_bf16", test_arm64_sve_bf16}, - {"test_arm64_sve_f32mm_f64mm", test_arm64_sve_f32mm_f64mm}, - {"test_arm64_sve2_fp_convert", test_arm64_sve2_fp_convert}, - {"test_arm64_sve2_fp_pairwise_flogb", - test_arm64_sve2_fp_pairwise_flogb}, - {"test_arm64_sve2_fmlal", test_arm64_sve2_fmlal}, - {"test_arm64_sve2_widen_add_shift", - test_arm64_sve2_widen_add_shift}, - {"test_arm64_sve2_addhn", test_arm64_sve2_addhn}, - {"test_arm64_sve2_xtn", test_arm64_sve2_xtn}, - {"test_arm64_sve2_shift_narrow", - test_arm64_sve2_shift_narrow}, - {"test_arm64_sve2_shift_accumulate", - test_arm64_sve2_shift_accumulate}, - {"test_arm64_sve2_shift_insert", - test_arm64_sve2_shift_insert}, - {"test_arm64_sve2_sat_unary", test_arm64_sve2_sat_unary}, - {"test_arm64_sve2_adalp", test_arm64_sve2_adalp}, - {"test_arm64_sve2_halving_add_sub", - test_arm64_sve2_halving_add_sub}, - {"test_arm64_sve2_pairwise_pred", - test_arm64_sve2_pairwise_pred}, - {"test_arm64_sve2_saturating_add_sub", - test_arm64_sve2_saturating_add_sub}, - {"test_arm64_sve2_int_estimate", - test_arm64_sve2_int_estimate}, - {"test_arm64_sve2_variable_shift", - test_arm64_sve2_variable_shift}, - {"test_arm64_sve2_eor_adcl", test_arm64_sve2_eor_adcl}, - {"test_arm64_sve2_bitperm", test_arm64_sve2_bitperm}, - {"test_arm64_sve2_match_hist", test_arm64_sve2_match_hist}, - {"test_arm64_sve2_crypto", test_arm64_sve2_crypto}, - {"test_arm64_sve2_ext", test_arm64_sve2_ext}, - {"test_arm64_sve2_splice", test_arm64_sve2_splice}, - {"test_arm64_sve2_tbl_tbx", test_arm64_sve2_tbl_tbx}, - {"test_arm64_sve2_ld1ro", test_arm64_sve2_ld1ro}, - {"test_arm64_mte_sve_contiguous_access", - test_arm64_mte_sve_contiguous_access}, - {"test_arm64_mte_sve_gather_scatter_sizem1", - test_arm64_mte_sve_gather_scatter_sizem1}, - {"test_arm64_mte_sve_whole_register_access", - test_arm64_mte_sve_whole_register_access}, - {"test_arm64_sve_contiguous_store_fault_no_partial", - test_arm64_sve_contiguous_store_fault_no_partial}, - {"test_arm64_sve_scatter_store_fault_no_partial", - test_arm64_sve_scatter_store_fault_no_partial}, - {"test_arm64_sve_ldff1_split_first_element", - test_arm64_sve_ldff1_split_first_element}, - {"test_arm64_sve_ldnf1_split_first_element", - test_arm64_sve_ldnf1_split_first_element}, - {"test_arm64_mte_tag_split_lifecycle", - test_arm64_mte_tag_split_lifecycle}, - {"test_arm64_mte_stgp", test_arm64_mte_stgp}, - {"test_arm64_mte_dc_zva_checked", - test_arm64_mte_dc_zva_checked}, - {"test_arm64_mte_dc_zva_original_fault_addr", - test_arm64_mte_dc_zva_original_fault_addr}, - {"test_arm64_mte_dc_gva_gzva", test_arm64_mte_dc_gva_gzva}, - {"test_arm64_mte_dc_gva_probe", test_arm64_mte_dc_gva_probe}, - {"test_arm64_mte_cache_ops", test_arm64_mte_cache_ops}, - {"test_arm64_generic_timer_state", test_arm64_generic_timer_state}, - {"test_arm64_pmu_counter_delta", test_arm64_pmu_counter_delta}, - {"test_arm64_pmu_pmuv3p5_event_counter", - test_arm64_pmu_pmuv3p5_event_counter}, - {"test_arm64_pmu_el2_hlp_long_counter", - test_arm64_pmu_el2_hlp_long_counter}, - {"test_arm64_pmu_el0_direct_counter_access", - test_arm64_pmu_el0_direct_counter_access}, - {"test_arm64_pmu_effective_mdcr_el2", - test_arm64_pmu_effective_mdcr_el2}, - {"test_arm64_pmu_pmcr_n_from_mdcr_el2", - test_arm64_pmu_pmcr_n_from_mdcr_el2}, - {"test_arm64_vhe_el12_aliases", test_arm64_vhe_el12_aliases}, - {"test_arm64_mte_requires_max", test_arm64_mte_requires_max}, - {NULL, NULL}}; +TEST_LIST = { + {"test_arm64_until", test_arm64_until}, + {"test_arm64_code_patching", test_arm64_code_patching}, + {"test_arm64_code_patching_count", test_arm64_code_patching_count}, + {"test_arm64_callback_active_tb_smc", test_arm64_callback_active_tb_smc}, + {"test_arm64_guest_store_cached_tb_smc", + test_arm64_guest_store_cached_tb_smc}, + {"test_arm64_guest_smc_cache_maintenance", + test_arm64_guest_smc_cache_maintenance}, + {"test_arm64_v8_cas", test_arm64_v8_cas}, + {"test_arm64_lse_rcpc_unaligned", test_arm64_lse_rcpc_unaligned}, + {"test_arm64_lse_signed_minmax_byte", test_arm64_lse_signed_minmax_byte}, + {"test_arm64_lse_rcpc_id_registers", test_arm64_lse_rcpc_id_registers}, + {"test_arm64_lse_rcpc_a72_rejects", test_arm64_lse_rcpc_a72_rejects}, + {"test_arm64_dgh_hint", test_arm64_dgh_hint}, + {"test_arm64_read_sctlr", test_arm64_read_sctlr}, + {"test_arm64_eret_el1_to_el0", test_arm64_eret_el1_to_el0}, + {"test_arm64_eret_illegal_spsr", test_arm64_eret_illegal_spsr}, + {"test_arm64_hook_insn_mrs", test_arm64_hook_insn_mrs}, + {"test_arm64_hook_insn_wfi", test_arm64_hook_insn_wfi}, + {"test_arm64_correct_address_in_small_jump_hook", + test_arm64_correct_address_in_small_jump_hook}, + {"test_arm64_correct_address_in_long_jump_hook", + test_arm64_correct_address_in_long_jump_hook}, + {"test_arm64_block_sync_pc", test_arm64_block_sync_pc}, + {"test_arm64_block_invalid_mem_read_write_sync", + test_arm64_block_invalid_mem_read_write_sync}, + {"test_arm64_mmu", test_arm64_mmu}, + {"test_arm64_pc_wrap", test_arm64_pc_wrap}, + {"test_arm64_mem_prot_regress", test_arm64_mem_prot_regress}, + {"test_arm64_mem_hook_read_write", test_arm64_mem_hook_read_write}, + {"test_arm64_pc_guarantee", test_arm64_pc_guarantee}, + {"test_arm64_sve_id_registers", test_arm64_sve_id_registers}, + {"test_arm64_sme_foundation", test_arm64_sme_foundation}, + {"test_arm64_sme_svlength", test_arm64_sme_svlength}, + {"test_arm64_sme_nonstreaming_sve_ffr", + test_arm64_sme_nonstreaming_sve_ffr}, + {"test_arm64_sme_nonstreaming_sve_misc", + test_arm64_sme_nonstreaming_sve_misc}, + {"test_arm64_sme_zero_mova", test_arm64_sme_zero_mova}, + {"test_arm64_sme_context_save_restore", + test_arm64_sme_context_save_restore}, + {"test_arm64_context_debug_lifecycle", + test_arm64_context_debug_lifecycle}, + {"test_arm64_sme_mova_q_horizontal", test_arm64_sme_mova_q_horizontal}, + {"test_arm64_sme_adda", test_arm64_sme_adda}, + {"test_arm64_sme_ldstr", test_arm64_sme_ldstr}, + {"test_arm64_sme_ldst1", test_arm64_sme_ldst1}, + {"test_arm64_sme_ldst1_fault_no_partial", + test_arm64_sme_ldst1_fault_no_partial}, + {"test_arm64_sme_psel", test_arm64_sme_psel}, + {"test_arm64_sme_ldst1_mte", test_arm64_sme_ldst1_mte}, + {"test_arm64_sme_imopa", test_arm64_sme_imopa}, + {"test_arm64_sme_fpout", test_arm64_sme_fpout}, + {"test_arm64_advsimd_aes_sha256", test_arm64_advsimd_aes_sha256}, + {"test_arm64_advsimd_sha512_gating", test_arm64_advsimd_sha512_gating}, + {"test_arm64_i8mm_advsimd", test_arm64_i8mm_advsimd}, + {"test_arm64_bf16_advsimd", test_arm64_bf16_advsimd}, + {"test_arm64_pauth_vanilla", test_arm64_pauth_vanilla}, + {"test_arm64_pauth_ctl", test_arm64_pauth_ctl}, + {"test_arm64_mte_register_only", test_arm64_mte_register_only}, + {"test_arm64_mte_ata_tag_generation", test_arm64_mte_ata_tag_generation}, + {"test_arm64_mte_tag_load_store", test_arm64_mte_tag_load_store}, + {"test_arm64_mte_tag_snapshot", test_arm64_mte_tag_snapshot}, + {"test_arm64_mte_tag_multiple", test_arm64_mte_tag_multiple}, + {"test_arm64_mte_checked_scalar_access", + test_arm64_mte_checked_scalar_access}, + {"test_arm64_mte_tco_msr_imm", test_arm64_mte_tco_msr_imm}, + {"test_arm64_mte_simd_fp_single_access", + test_arm64_mte_simd_fp_single_access}, + {"test_arm64_mte_advsimd_struct_range", + test_arm64_mte_advsimd_struct_range}, + {"test_arm64_mte_lse_atomic_asym_sync_no_side_effect", + test_arm64_mte_lse_atomic_asym_sync_no_side_effect}, + {"test_arm64_mte_ldapr_sync_tag_check", + test_arm64_mte_ldapr_sync_tag_check}, + {"test_arm64_mte_lse_cas_asym_async_side_effect", + test_arm64_mte_lse_cas_asym_async_side_effect}, + {"test_arm64_mte_exclusive_asym_access", + test_arm64_mte_exclusive_asym_access}, + {"test_arm64_mte_sp_addressing_tagchecked", + test_arm64_mte_sp_addressing_tagchecked}, + {"test_arm64_mte_sp_writeback_tagchecked", + test_arm64_mte_sp_writeback_tagchecked}, + {"test_arm64_mte_pair_sp_tagchecked", test_arm64_mte_pair_sp_tagchecked}, + {"test_arm64_mte_pac_load_sp_tagchecked", + test_arm64_mte_pac_load_sp_tagchecked}, + {"test_arm64_mte_tcma0_tag_zero_unchecked", + test_arm64_mte_tcma0_tag_zero_unchecked}, + {"test_arm64_mte_ldapur_stlur_unchecked", + test_arm64_mte_ldapur_stlur_unchecked}, + {"test_arm64_mte_ldapur_stlur_variants_unchecked", + test_arm64_mte_ldapur_stlur_variants_unchecked}, + {"test_arm64_mte_unpriv_sp_no_tag_check", + test_arm64_mte_unpriv_sp_no_tag_check}, + {"test_arm64_mte_unpriv_async_tag_check", + test_arm64_mte_unpriv_async_tag_check}, + {"test_arm64_mte_page_attrs", test_arm64_mte_page_attrs}, + {"test_arm64_bti_guarded_page", test_arm64_bti_guarded_page}, + {"test_arm64_mte_hcr_dct", test_arm64_mte_hcr_dct}, + {"test_arm64_mte_cross_page_fault_priority", + test_arm64_mte_cross_page_fault_priority}, + {"test_arm64_mte_ata_disabled_tag_op_probe", + test_arm64_mte_ata_disabled_tag_op_probe}, + {"test_arm64_sve2_non_temporal_gather_scatter", + test_arm64_sve2_non_temporal_gather_scatter}, + {"test_arm64_sve2_bitwise_ternary", test_arm64_sve2_bitwise_ternary}, + {"test_arm64_sve2_xar", test_arm64_sve2_xar}, + {"test_arm64_sve2_pmull", test_arm64_sve2_pmull}, + {"test_arm64_sve2_mul_base", test_arm64_sve2_mul_base}, + {"test_arm64_sve2_mul_indexed", test_arm64_sve2_mul_indexed}, + {"test_arm64_sve2_widen_indexed", test_arm64_sve2_widen_indexed}, + {"test_arm64_sve2_widen_accumulate", test_arm64_sve2_widen_accumulate}, + {"test_arm64_sve2_abs_accumulate", test_arm64_sve2_abs_accumulate}, + {"test_arm64_sve2_cadd_sqcadd", test_arm64_sve2_cadd_sqcadd}, + {"test_arm64_sve2_sqrdmla", test_arm64_sve2_sqrdmla}, + {"test_arm64_sve2_complex_dot", test_arm64_sve2_complex_dot}, + {"test_arm64_sve_i8mm", test_arm64_sve_i8mm}, + {"test_arm64_sve_bf16", test_arm64_sve_bf16}, + {"test_arm64_sve_f32mm_f64mm", test_arm64_sve_f32mm_f64mm}, + {"test_arm64_sve2_fp_convert", test_arm64_sve2_fp_convert}, + {"test_arm64_sve2_fp_pairwise_flogb", test_arm64_sve2_fp_pairwise_flogb}, + {"test_arm64_sve2_fmlal", test_arm64_sve2_fmlal}, + {"test_arm64_sve2_widen_add_shift", test_arm64_sve2_widen_add_shift}, + {"test_arm64_sve2_addhn", test_arm64_sve2_addhn}, + {"test_arm64_sve2_xtn", test_arm64_sve2_xtn}, + {"test_arm64_sve2_shift_narrow", test_arm64_sve2_shift_narrow}, + {"test_arm64_sve2_shift_accumulate", test_arm64_sve2_shift_accumulate}, + {"test_arm64_sve2_shift_insert", test_arm64_sve2_shift_insert}, + {"test_arm64_sve2_sat_unary", test_arm64_sve2_sat_unary}, + {"test_arm64_sve2_adalp", test_arm64_sve2_adalp}, + {"test_arm64_sve2_halving_add_sub", test_arm64_sve2_halving_add_sub}, + {"test_arm64_sve2_pairwise_pred", test_arm64_sve2_pairwise_pred}, + {"test_arm64_sve2_saturating_add_sub", test_arm64_sve2_saturating_add_sub}, + {"test_arm64_sve2_int_estimate", test_arm64_sve2_int_estimate}, + {"test_arm64_sve2_variable_shift", test_arm64_sve2_variable_shift}, + {"test_arm64_sve2_eor_adcl", test_arm64_sve2_eor_adcl}, + {"test_arm64_sve2_bitperm", test_arm64_sve2_bitperm}, + {"test_arm64_sve2_match_hist", test_arm64_sve2_match_hist}, + {"test_arm64_sve2_crypto", test_arm64_sve2_crypto}, + {"test_arm64_sve2_ext", test_arm64_sve2_ext}, + {"test_arm64_sve2_splice", test_arm64_sve2_splice}, + {"test_arm64_sve2_tbl_tbx", test_arm64_sve2_tbl_tbx}, + {"test_arm64_sve2_ld1ro", test_arm64_sve2_ld1ro}, + {"test_arm64_mte_sve_contiguous_access", + test_arm64_mte_sve_contiguous_access}, + {"test_arm64_mte_sve_gather_scatter_sizem1", + test_arm64_mte_sve_gather_scatter_sizem1}, + {"test_arm64_mte_sve_whole_register_access", + test_arm64_mte_sve_whole_register_access}, + {"test_arm64_sve_contiguous_store_fault_no_partial", + test_arm64_sve_contiguous_store_fault_no_partial}, + {"test_arm64_sve_scatter_store_fault_no_partial", + test_arm64_sve_scatter_store_fault_no_partial}, + {"test_arm64_sve_ldff1_split_first_element", + test_arm64_sve_ldff1_split_first_element}, + {"test_arm64_sve_ldnf1_split_first_element", + test_arm64_sve_ldnf1_split_first_element}, + {"test_arm64_mte_tag_split_lifecycle", test_arm64_mte_tag_split_lifecycle}, + {"test_arm64_mte_stgp", test_arm64_mte_stgp}, + {"test_arm64_mte_dc_zva_checked", test_arm64_mte_dc_zva_checked}, + {"test_arm64_mte_dc_zva_original_fault_addr", + test_arm64_mte_dc_zva_original_fault_addr}, + {"test_arm64_mte_dc_gva_gzva", test_arm64_mte_dc_gva_gzva}, + {"test_arm64_mte_dc_gva_probe", test_arm64_mte_dc_gva_probe}, + {"test_arm64_mte_cache_ops", test_arm64_mte_cache_ops}, + {"test_arm64_generic_timer_state", test_arm64_generic_timer_state}, + {"test_arm64_pmu_counter_delta", test_arm64_pmu_counter_delta}, + {"test_arm64_pmu_pmuv3p5_event_counter", + test_arm64_pmu_pmuv3p5_event_counter}, + {"test_arm64_pmu_el2_hlp_long_counter", + test_arm64_pmu_el2_hlp_long_counter}, + {"test_arm64_pmu_el0_direct_counter_access", + test_arm64_pmu_el0_direct_counter_access}, + {"test_arm64_pmu_effective_mdcr_el2", test_arm64_pmu_effective_mdcr_el2}, + {"test_arm64_pmu_pmcr_n_from_mdcr_el2", + test_arm64_pmu_pmcr_n_from_mdcr_el2}, + {"test_arm64_vhe_el12_aliases", test_arm64_vhe_el12_aliases}, + {"test_arm64_mte_requires_max", test_arm64_mte_requires_max}, + {NULL, NULL}}; diff --git a/tests/unit/test_ctl.c b/tests/unit/test_ctl.c index 0303027c2f..04af866f9c 100644 --- a/tests/unit/test_ctl.c +++ b/tests/unit/test_ctl.c @@ -64,6 +64,715 @@ static void test_uc_ctl_exits(void) OK(uc_close(uc)); } +static void test_uc_ctl_exits_boundaries(void) +{ + const char code[] = "\x90"; + uint64_t exits[] = { + code_start + 0x30, + code_start + 0x10, + code_start + 0x30, + code_start + 0x20, + }; + uint64_t output[4] = { + UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX, + }; + size_t count; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, + sizeof(code) - 1); + uc_assert_err(UC_ERR_ARG, uc_ctl_get_exits_cnt(uc, &count)); + uc_assert_err(UC_ERR_ARG, uc_ctl_set_exits(uc, exits, 4)); + + OK(uc_ctl_exits_enable(uc)); + OK(uc_ctl_set_exits(uc, exits, 4)); + OK(uc_ctl_get_exits_cnt(uc, &count)); + TEST_CHECK(count == 3); + uc_assert_err(UC_ERR_ARG, uc_ctl_get_exits(uc, output, 2)); + TEST_CHECK(output[0] == UINT64_MAX && output[1] == UINT64_MAX); + OK(uc_ctl_get_exits(uc, output, 4)); + TEST_CHECK(output[0] == code_start + 0x10); + TEST_CHECK(output[1] == code_start + 0x20); + TEST_CHECK(output[2] == code_start + 0x30); + TEST_CHECK(output[3] == UINT64_MAX); + + OK(uc_ctl_exits_disable(uc)); + uc_assert_err(UC_ERR_ARG, uc_ctl_get_exits_cnt(uc, &count)); + OK(uc_ctl_exits_enable(uc)); + OK(uc_ctl_get_exits_cnt(uc, &count)); + TEST_CHECK(count == 0); + + OK(uc_close(uc)); +} + +static void test_uc_timeout_reuse(void) +{ + const char loop[] = "\xeb\xfe"; + const char nop[] = "\x90"; + size_t timed_out; + uint32_t eip; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, loop, + sizeof(loop) - 1); + OK(uc_mem_write(uc, code_start + 0x100, nop, sizeof(nop) - 1)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(loop) - 1, + UC_SECOND_SCALE / 10, 0)); + OK(uc_query(uc, UC_QUERY_TIMEOUT, &timed_out)); + TEST_CHECK(timed_out == 1); + + OK(uc_emu_start(uc, code_start + 0x100, + code_start + 0x100 + sizeof(nop) - 1, 0, 1)); + OK(uc_query(uc, UC_QUERY_TIMEOUT, &timed_out)); + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + TEST_CHECK(timed_out == 0); + TEST_CHECK(eip == code_start + 0x100 + sizeof(nop) - 1); + + OK(uc_close(uc)); +} + +static void test_uc_timeout_max_tb(void) +{ + enum { + max_tb_insns = 512, + nop_insns = max_tb_insns - 1, + loop_size = nop_insns + 5, + }; + uint8_t code[loop_size]; + const int32_t displacement = -(int32_t)sizeof(code); + size_t timed_out; + uint32_t eip; + uc_engine *uc; + + memset(code, 0x90, nop_insns); + code[nop_insns] = 0xe9; + memcpy(&code[nop_insns + 1], &displacement, sizeof(displacement)); + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, (const char *)code, + sizeof(code)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), + UC_SECOND_SCALE / 20, 0)); + OK(uc_query(uc, UC_QUERY_TIMEOUT, &timed_out)); + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + TEST_CHECK(timed_out == 1); + TEST_CHECK_(eip == code_start, "eip=0x%x", eip); + + OK(uc_close(uc)); +} + +static void test_uc_reg_sized(void) +{ + const uint64_t initial_rax = UINT64_C(0x0123456789abcdef); + const uint64_t rejected_rax = UINT64_C(0xfedcba9876543210); + const uint64_t initial_xmm[2] = { + UINT64_C(0x0011223344556677), + UINT64_C(0x8899aabbccddeeff), + }; + const uint64_t rejected_xmm[2] = { + UINT64_C(0xffeeddccbbaa9988), + UINT64_C(0x7766554433221100), + }; + uint64_t rax; + uint64_t xmm[2]; + size_t size; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + + size = sizeof(initial_rax); + OK(uc_reg_write2(uc, UC_X86_REG_RAX, &initial_rax, &size)); + TEST_CHECK(size == sizeof(initial_rax)); + size = sizeof(initial_xmm); + OK(uc_reg_write2(uc, UC_X86_REG_XMM0, initial_xmm, &size)); + TEST_CHECK(size == sizeof(initial_xmm)); + + rax = 0; + size = sizeof(rax); + OK(uc_reg_read2(uc, UC_X86_REG_RAX, &rax, &size)); + TEST_CHECK(size == sizeof(rax)); + TEST_CHECK(rax == initial_rax); + memset(xmm, 0, sizeof(xmm)); + size = sizeof(xmm); + OK(uc_reg_read2(uc, UC_X86_REG_XMM0, xmm, &size)); + TEST_CHECK(size == sizeof(xmm)); + TEST_CHECK(memcmp(xmm, initial_xmm, sizeof(xmm)) == 0); + + rax = UINT64_MAX; + size = sizeof(rax) - 1; + uc_assert_err(UC_ERR_OVERFLOW, + uc_reg_read2(uc, UC_X86_REG_RAX, &rax, &size)); + TEST_CHECK(rax == UINT64_MAX); + size = sizeof(rejected_rax) - 1; + uc_assert_err(UC_ERR_OVERFLOW, + uc_reg_write2(uc, UC_X86_REG_RAX, &rejected_rax, &size)); + OK(uc_reg_read(uc, UC_X86_REG_RAX, &rax)); + TEST_CHECK(rax == initial_rax); + + xmm[0] = UINT64_MAX; + xmm[1] = UINT64_MAX; + size = sizeof(xmm) - 1; + uc_assert_err(UC_ERR_OVERFLOW, + uc_reg_read2(uc, UC_X86_REG_XMM0, xmm, &size)); + TEST_CHECK(xmm[0] == UINT64_MAX && xmm[1] == UINT64_MAX); + size = sizeof(rejected_xmm) - 1; + uc_assert_err(UC_ERR_OVERFLOW, + uc_reg_write2(uc, UC_X86_REG_XMM0, rejected_xmm, &size)); + OK(uc_reg_read(uc, UC_X86_REG_XMM0, xmm)); + TEST_CHECK(memcmp(xmm, initial_xmm, sizeof(xmm)) == 0); + + OK(uc_close(uc)); +} + +static void test_uc_reg_batch(void) +{ + const int regs[] = { + UC_X86_REG_RAX, + UC_X86_REG_XMM0, + UC_X86_REG_RIP, + }; + uint64_t rax = UINT64_C(0x1122334455667788); + uint64_t xmm[2] = { + UINT64_C(0x1020304050607080), + UINT64_C(0x90a0b0c0d0e0f000), + }; + uint64_t rip = code_start + 0x20; + void *write_values[] = {&rax, xmm, &rip}; + uint64_t read_rax = 0; + uint64_t read_xmm[2] = {0}; + uint64_t read_rip = 0; + void *read_values[] = {&read_rax, read_xmm, &read_rip}; + const void *const_values[] = {&rax, xmm, &rip}; + size_t sizes[] = {sizeof(rax), sizeof(xmm), sizeof(rip)}; + size_t read_sizes[] = { + sizeof(read_rax), + sizeof(read_xmm), + sizeof(read_rip), + }; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + + OK(uc_reg_write_batch(uc, regs, write_values, 3)); + OK(uc_reg_read_batch(uc, regs, read_values, 3)); + TEST_CHECK(read_rax == rax); + TEST_CHECK(memcmp(read_xmm, xmm, sizeof(xmm)) == 0); + TEST_CHECK(read_rip == rip); + + rax = UINT64_C(0x8877665544332211); + xmm[0] = UINT64_C(0xf0e0d0c0b0a09080); + xmm[1] = UINT64_C(0x7060504030201000); + rip = code_start + 0x40; + OK(uc_reg_write_batch2(uc, regs, const_values, sizes, 3)); + TEST_CHECK(sizes[0] == sizeof(rax)); + TEST_CHECK(sizes[1] == sizeof(xmm)); + TEST_CHECK(sizes[2] == sizeof(rip)); + memset(read_values[0], 0, sizeof(read_rax)); + memset(read_values[1], 0, sizeof(read_xmm)); + memset(read_values[2], 0, sizeof(read_rip)); + OK(uc_reg_read_batch2(uc, regs, read_values, read_sizes, 3)); + TEST_CHECK(read_sizes[0] == sizeof(read_rax)); + TEST_CHECK(read_sizes[1] == sizeof(read_xmm)); + TEST_CHECK(read_sizes[2] == sizeof(read_rip)); + TEST_CHECK(read_rax == rax); + TEST_CHECK(memcmp(read_xmm, xmm, sizeof(xmm)) == 0); + TEST_CHECK(read_rip == rip); + + OK(uc_close(uc)); +} + +static void test_uc_reg_batch_partial_failure(void) +{ + const int invalid_regs[] = { + UC_X86_REG_RAX, + UC_X86_REG_ENDING, + UC_X86_REG_RBX, + }; + const int overflow_regs[] = { + UC_X86_REG_RAX, + UC_X86_REG_XMM0, + UC_X86_REG_RBX, + }; + uint64_t rax = 1; + uint64_t invalid = 2; + uint64_t rbx = 3; + void *write_values[] = {&rax, &invalid, &rbx}; + uint64_t read_rax = UINT64_MAX; + uint64_t read_invalid = UINT64_MAX; + uint64_t read_rbx = UINT64_MAX; + void *read_values[] = {&read_rax, &read_invalid, &read_rbx}; + uint64_t xmm[2] = {5, 6}; + const void *overflow_values[] = {&rax, xmm, &rbx}; + uint64_t read_xmm[2] = {UINT64_MAX, UINT64_MAX}; + void *overflow_read_values[] = {&read_rax, read_xmm, &read_rbx}; + size_t sizes[] = {sizeof(rax), sizeof(xmm) - 1, sizeof(rbx)}; + uint64_t value; + uc_err error; + bool strict_errors; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + value = 10; + OK(uc_reg_write(uc, UC_X86_REG_RAX, &value)); + value = 20; + OK(uc_reg_write(uc, UC_X86_REG_RBX, &value)); + + strict_errors = getenv("UC_IGNORE_REG_BREAK") != NULL; + error = uc_reg_write_batch(uc, invalid_regs, write_values, 3); + TEST_CHECK(error == (strict_errors ? UC_ERR_ARG : UC_ERR_OK)); + OK(uc_reg_read(uc, UC_X86_REG_RAX, &value)); + TEST_CHECK(value == rax); + OK(uc_reg_read(uc, UC_X86_REG_RBX, &value)); + TEST_CHECK(value == (strict_errors ? 20 : rbx)); + error = uc_reg_read_batch(uc, invalid_regs, read_values, 3); + TEST_CHECK(error == (strict_errors ? UC_ERR_ARG : UC_ERR_OK)); + TEST_CHECK(read_rax == rax); + TEST_CHECK(read_invalid == UINT64_MAX); + TEST_CHECK(read_rbx == (strict_errors ? UINT64_MAX : value)); + + rax = 4; + value = 30; + OK(uc_reg_write(uc, UC_X86_REG_RAX, &value)); + value = 40; + OK(uc_reg_write(uc, UC_X86_REG_RBX, &value)); + uc_assert_err( + UC_ERR_OVERFLOW, + uc_reg_write_batch2(uc, overflow_regs, overflow_values, sizes, 3)); + OK(uc_reg_read(uc, UC_X86_REG_RAX, &value)); + TEST_CHECK(value == rax); + OK(uc_reg_read(uc, UC_X86_REG_RBX, &value)); + TEST_CHECK(value == 40); + + read_rax = UINT64_MAX; + read_xmm[0] = UINT64_MAX; + read_xmm[1] = UINT64_MAX; + read_rbx = UINT64_MAX; + sizes[0] = sizeof(read_rax); + sizes[1] = sizeof(read_xmm) - 1; + sizes[2] = sizeof(read_rbx); + uc_assert_err( + UC_ERR_OVERFLOW, + uc_reg_read_batch2(uc, overflow_regs, overflow_read_values, sizes, 3)); + TEST_CHECK(read_rax == rax); + TEST_CHECK(read_xmm[0] == UINT64_MAX && read_xmm[1] == UINT64_MAX); + TEST_CHECK(read_rbx == UINT64_MAX); + + OK(uc_close(uc)); +} + +static void test_uc_context_reg_apis(void) +{ + const int regs[] = { + UC_X86_REG_RAX, + UC_X86_REG_XMM0, + UC_X86_REG_RIP, + }; + uint64_t rax = UINT64_C(0x0123456789abcdef); + uint64_t xmm[2] = { + UINT64_C(0x1111222233334444), + UINT64_C(0x5555666677778888), + }; + uint64_t rip = code_start + 0x20; + void *write_values[] = {&rax, xmm, &rip}; + const void *const_values[] = {&rax, xmm, &rip}; + uint64_t read_rax = 0; + uint64_t read_xmm[2] = {0}; + uint64_t read_rip = 0; + void *read_values[] = {&read_rax, read_xmm, &read_rip}; + size_t sizes[] = {sizeof(rax), sizeof(xmm), sizeof(rip)}; + size_t read_sizes[] = { + sizeof(read_rax), + sizeof(read_xmm), + sizeof(read_rip), + }; + size_t context_size; + uc_context *context; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + context_size = uc_context_size(uc); + TEST_CHECK(context_size > sizeof(rax) + sizeof(xmm)); + TEST_CHECK(uc_context_size(uc) == context_size); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + sizes[0] = sizeof(rax); + OK(uc_context_reg_write2(context, UC_X86_REG_RAX, &rax, &sizes[0])); + TEST_CHECK(sizes[0] == sizeof(rax)); + sizes[1] = sizeof(xmm); + OK(uc_context_reg_write2(context, UC_X86_REG_XMM0, xmm, &sizes[1])); + TEST_CHECK(sizes[1] == sizeof(xmm)); + read_rax = 0; + read_sizes[0] = sizeof(read_rax); + OK(uc_context_reg_read2(context, UC_X86_REG_RAX, &read_rax, + &read_sizes[0])); + TEST_CHECK(read_sizes[0] == sizeof(read_rax)); + TEST_CHECK(read_rax == rax); + memset(read_xmm, 0, sizeof(read_xmm)); + read_sizes[1] = sizeof(read_xmm); + OK(uc_context_reg_read2(context, UC_X86_REG_XMM0, read_xmm, + &read_sizes[1])); + TEST_CHECK(read_sizes[1] == sizeof(read_xmm)); + TEST_CHECK(memcmp(read_xmm, xmm, sizeof(xmm)) == 0); + + read_rax = UINT64_MAX; + read_sizes[0] = sizeof(read_rax) - 1; + uc_assert_err(UC_ERR_OVERFLOW, + uc_context_reg_read2(context, UC_X86_REG_RAX, &read_rax, + &read_sizes[0])); + TEST_CHECK(read_rax == UINT64_MAX); + read_sizes[0] = sizeof(rax) - 1; + read_rax = rax + 1; + uc_assert_err(UC_ERR_OVERFLOW, + uc_context_reg_write2(context, UC_X86_REG_RAX, &read_rax, + &read_sizes[0])); + OK(uc_context_reg_read(context, UC_X86_REG_RAX, &read_rax)); + TEST_CHECK(read_rax == rax); + read_xmm[0] = UINT64_MAX; + read_xmm[1] = UINT64_MAX; + read_sizes[1] = sizeof(read_xmm) - 1; + uc_assert_err(UC_ERR_OVERFLOW, + uc_context_reg_read2(context, UC_X86_REG_XMM0, read_xmm, + &read_sizes[1])); + TEST_CHECK(read_xmm[0] == UINT64_MAX && read_xmm[1] == UINT64_MAX); + read_sizes[1] = sizeof(read_xmm) - 1; + uc_assert_err(UC_ERR_OVERFLOW, + uc_context_reg_write2(context, UC_X86_REG_XMM0, read_xmm, + &read_sizes[1])); + OK(uc_context_reg_read(context, UC_X86_REG_XMM0, read_xmm)); + TEST_CHECK(memcmp(read_xmm, xmm, sizeof(xmm)) == 0); + + rax = UINT64_C(0x8877665544332211); + xmm[0] = UINT64_C(0x9999aaaabbbbcccc); + xmm[1] = UINT64_C(0xddddeeeeffff0000); + rip = code_start + 0x40; + OK(uc_context_reg_write_batch(context, regs, write_values, 3)); + OK(uc_context_reg_read_batch(context, regs, read_values, 3)); + TEST_CHECK(read_rax == rax); + TEST_CHECK(memcmp(read_xmm, xmm, sizeof(xmm)) == 0); + TEST_CHECK(read_rip == rip); + OK(uc_context_restore(uc, context)); + OK(uc_reg_read(uc, UC_X86_REG_RIP, &read_rip)); + TEST_CHECK(read_rip == rip); + + rax = UINT64_C(0x1020304050607080); + xmm[0] = UINT64_C(0x0f1e2d3c4b5a6978); + xmm[1] = UINT64_C(0x8796a5b4c3d2e1f0); + rip = code_start + 0x60; + sizes[0] = sizeof(rax); + sizes[1] = sizeof(xmm); + sizes[2] = sizeof(rip); + OK(uc_context_reg_write_batch2(context, regs, const_values, sizes, 3)); + TEST_CHECK(sizes[0] == sizeof(rax)); + TEST_CHECK(sizes[1] == sizeof(xmm)); + TEST_CHECK(sizes[2] == sizeof(rip)); + read_rax = 0; + memset(read_xmm, 0, sizeof(read_xmm)); + read_rip = 0; + read_sizes[0] = sizeof(read_rax); + read_sizes[1] = sizeof(read_xmm); + read_sizes[2] = sizeof(read_rip); + OK(uc_context_reg_read_batch2(context, regs, read_values, read_sizes, 3)); + TEST_CHECK(read_sizes[0] == sizeof(read_rax)); + TEST_CHECK(read_sizes[1] == sizeof(read_xmm)); + TEST_CHECK(read_sizes[2] == sizeof(read_rip)); + TEST_CHECK(read_rax == rax); + TEST_CHECK(memcmp(read_xmm, xmm, sizeof(xmm)) == 0); + TEST_CHECK(read_rip == rip); + OK(uc_context_restore(uc, context)); + OK(uc_reg_read(uc, UC_X86_REG_RIP, &read_rip)); + TEST_CHECK(read_rip == rip); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + +static void test_uc_context_batch_partial_failure(void) +{ + const int invalid_regs[] = { + UC_X86_REG_RAX, + UC_X86_REG_ENDING, + UC_X86_REG_RBX, + }; + const int overflow_regs[] = { + UC_X86_REG_RAX, + UC_X86_REG_XMM0, + UC_X86_REG_RBX, + }; + uint64_t rax = 1; + uint64_t invalid = 2; + uint64_t rbx = 3; + void *write_values[] = {&rax, &invalid, &rbx}; + uint64_t read_rax = UINT64_MAX; + uint64_t read_invalid = UINT64_MAX; + uint64_t read_rbx = UINT64_MAX; + void *read_values[] = {&read_rax, &read_invalid, &read_rbx}; + uint64_t xmm[2] = {5, 6}; + const void *overflow_values[] = {&rax, xmm, &rbx}; + uint64_t read_xmm[2] = {UINT64_MAX, UINT64_MAX}; + void *overflow_read_values[] = {&read_rax, read_xmm, &read_rbx}; + size_t sizes[] = {sizeof(rax), sizeof(xmm) - 1, sizeof(rbx)}; + uint64_t value; + uc_err error; + bool strict_errors; + uc_context *context; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + value = 10; + OK(uc_context_reg_write(context, UC_X86_REG_RAX, &value)); + value = 20; + OK(uc_context_reg_write(context, UC_X86_REG_RBX, &value)); + + strict_errors = getenv("UC_IGNORE_REG_BREAK") != NULL; + error = uc_context_reg_write_batch(context, invalid_regs, write_values, 3); + TEST_CHECK(error == (strict_errors ? UC_ERR_ARG : UC_ERR_OK)); + OK(uc_context_reg_read(context, UC_X86_REG_RAX, &value)); + TEST_CHECK(value == rax); + OK(uc_context_reg_read(context, UC_X86_REG_RBX, &value)); + TEST_CHECK(value == (strict_errors ? 20 : rbx)); + error = uc_context_reg_read_batch(context, invalid_regs, read_values, 3); + TEST_CHECK(error == (strict_errors ? UC_ERR_ARG : UC_ERR_OK)); + TEST_CHECK(read_rax == rax); + TEST_CHECK(read_invalid == UINT64_MAX); + TEST_CHECK(read_rbx == (strict_errors ? UINT64_MAX : value)); + + rax = 4; + value = 30; + OK(uc_context_reg_write(context, UC_X86_REG_RAX, &value)); + value = 40; + OK(uc_context_reg_write(context, UC_X86_REG_RBX, &value)); + uc_assert_err(UC_ERR_OVERFLOW, + uc_context_reg_write_batch2(context, overflow_regs, + overflow_values, sizes, 3)); + OK(uc_context_reg_read(context, UC_X86_REG_RAX, &value)); + TEST_CHECK(value == rax); + OK(uc_context_reg_read(context, UC_X86_REG_RBX, &value)); + TEST_CHECK(value == 40); + + read_rax = UINT64_MAX; + read_xmm[0] = UINT64_MAX; + read_xmm[1] = UINT64_MAX; + read_rbx = UINT64_MAX; + sizes[0] = sizeof(read_rax); + sizes[1] = sizeof(read_xmm) - 1; + sizes[2] = sizeof(read_rbx); + uc_assert_err(UC_ERR_OVERFLOW, + uc_context_reg_read_batch2(context, overflow_regs, + overflow_read_values, sizes, 3)); + TEST_CHECK(read_rax == rax); + TEST_CHECK(read_xmm[0] == UINT64_MAX && read_xmm[1] == UINT64_MAX); + TEST_CHECK(read_rbx == UINT64_MAX); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + +typedef struct TestNestedTimeoutData { + uint64_t inner_address; + uint64_t inner_size; + uint64_t inner_timeout; + uc_err inner_error; + uc_err query_error; + size_t timed_out; + uint32_t calls; +} TestNestedTimeoutData; + +static void test_uc_nested_timeout_cb(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + TestNestedTimeoutData *data = (TestNestedTimeoutData *)user_data; + + data->calls++; + data->inner_error = + uc_emu_start(uc, data->inner_address, + data->inner_address + data->inner_size, + data->inner_timeout, 0); + data->query_error = uc_query(uc, UC_QUERY_TIMEOUT, &data->timed_out); +} + +static void test_uc_nested_timeout_case(uint64_t inner_timeout, + uint64_t outer_timeout, + bool outer_continues) +{ + const char outer_code[] = "\x40\x43"; /* inc eax; inc ebx */ + const char inner_code[] = "\xeb\xfe"; /* jmp inner_code */ + const char reuse_code[] = "\x41"; /* inc ecx */ + const uint64_t inner_address = code_start + 0x100; + const uint64_t reuse_address = code_start + 0x200; + TestNestedTimeoutData data = { + .inner_address = inner_address, + .inner_size = sizeof(inner_code) - 1, + .inner_timeout = inner_timeout, + }; + size_t timed_out; + uint32_t value; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, outer_code, + sizeof(outer_code) - 1); + OK(uc_mem_write(uc, inner_address, inner_code, sizeof(inner_code) - 1)); + OK(uc_mem_write(uc, reuse_address, reuse_code, sizeof(reuse_code) - 1)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, test_uc_nested_timeout_cb, &data, + code_start, code_start)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(outer_code) - 1, + outer_timeout, 0)); + TEST_CHECK(data.calls == 1); + TEST_CHECK(data.inner_error == UC_ERR_OK); + TEST_CHECK(data.query_error == UC_ERR_OK); + TEST_CHECK(data.timed_out == 1); + OK(uc_query(uc, UC_QUERY_TIMEOUT, &timed_out)); + TEST_CHECK(timed_out == 1); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &value)); + TEST_CHECK(value == (outer_continues ? 1 : 0)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &value)); + TEST_CHECK(value == (outer_continues ? 1 : 0)); + + OK(uc_hook_del(uc, hook)); + OK(uc_emu_start(uc, reuse_address, reuse_address + sizeof(reuse_code) - 1, + 0, 1)); + OK(uc_query(uc, UC_QUERY_TIMEOUT, &timed_out)); + TEST_CHECK(timed_out == 0); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &value)); + TEST_CHECK(value == 1); + + OK(uc_close(uc)); +} + +static void test_uc_nested_timeout(void) +{ + test_uc_nested_timeout_case(UC_SECOND_SCALE / 20, 0, true); + test_uc_nested_timeout_case(0, UC_SECOND_SCALE / 20, false); + test_uc_nested_timeout_case(UC_SECOND_SCALE / 50, + UC_SECOND_SCALE / 2, true); + test_uc_nested_timeout_case(UC_SECOND_SCALE / 2, + UC_SECOND_SCALE / 50, false); +} + +static void test_uc_nested_timeout_completion(void) +{ + const char outer_code[] = "\x40\x43"; /* inc eax; inc ebx */ + const char inner_code[] = "\x42"; /* inc edx */ + const char reuse_code[] = "\x41"; /* inc ecx */ + const uint64_t inner_address = code_start + 0x100; + const uint64_t reuse_address = code_start + 0x200; + TestNestedTimeoutData data = { + .inner_address = inner_address, + .inner_size = sizeof(inner_code) - 1, + .inner_timeout = UC_SECOND_SCALE / 2, + }; + size_t timed_out; + uint32_t value; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, outer_code, + sizeof(outer_code) - 1); + OK(uc_mem_write(uc, inner_address, inner_code, sizeof(inner_code) - 1)); + OK(uc_mem_write(uc, reuse_address, reuse_code, sizeof(reuse_code) - 1)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, test_uc_nested_timeout_cb, &data, + code_start, code_start)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(outer_code) - 1, + UC_SECOND_SCALE / 2, 0)); + TEST_CHECK(data.calls == 1); + TEST_CHECK(data.inner_error == UC_ERR_OK); + TEST_CHECK(data.query_error == UC_ERR_OK); + TEST_CHECK(data.timed_out == 0); + OK(uc_query(uc, UC_QUERY_TIMEOUT, &timed_out)); + TEST_CHECK(timed_out == 0); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &value)); + TEST_CHECK(value == 1); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &value)); + TEST_CHECK(value == 1); + OK(uc_reg_read(uc, UC_X86_REG_EDX, &value)); + TEST_CHECK(value == 1); + + OK(uc_hook_del(uc, hook)); + OK(uc_emu_start(uc, reuse_address, reuse_address + sizeof(reuse_code) - 1, + 0, 1)); + OK(uc_query(uc, UC_QUERY_TIMEOUT, &timed_out)); + TEST_CHECK(timed_out == 0); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &value)); + TEST_CHECK(value == 1); + + OK(uc_close(uc)); +} + +static void test_uc_invalid_hook_cb(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + uint32_t *calls = (uint32_t *)user_data; + + (*calls)++; +} + +static void test_uc_invalid_hook_types(void) +{ + const char code[] = "\x90"; + const int unknown_type = 1U << 30; + uint32_t calls = 0; + uc_hook hook; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); + + hook = UINTPTR_MAX; + uc_assert_err( + UC_ERR_HOOK, + uc_hook_add(uc, &hook, 0, test_uc_invalid_hook_cb, &calls, 1, 0)); + TEST_CHECK(hook == UINTPTR_MAX); + hook = UINTPTR_MAX; + uc_assert_err(UC_ERR_HOOK, + uc_hook_add(uc, &hook, unknown_type, test_uc_invalid_hook_cb, + &calls, 1, 0)); + TEST_CHECK(hook == UINTPTR_MAX); + hook = UINTPTR_MAX; + uc_assert_err(UC_ERR_HOOK, + uc_hook_add(uc, &hook, UC_HOOK_CODE | unknown_type, + test_uc_invalid_hook_cb, &calls, 1, 0)); + TEST_CHECK(hook == UINTPTR_MAX); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 1)); + TEST_CHECK(calls == 0); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, test_uc_invalid_hook_cb, &calls, 1, + 0)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 1)); + TEST_CHECK(calls == 1); + + OK(uc_close(uc)); +} + +static void test_uc_query_and_cpu_model(void) +{ + const char code[] = "\x90"; + size_t result; + int model; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, + sizeof(code) - 1); + uc_assert_err(UC_ERR_ARG, uc_query(uc, UC_QUERY_MODE + 100, &result)); + OK(uc_query(uc, UC_QUERY_ARCH, &result)); + TEST_CHECK(result == UC_ARCH_X86); + OK(uc_query(uc, UC_QUERY_MODE, &result)); + TEST_CHECK(result == UC_MODE_32); + OK(uc_query(uc, UC_QUERY_PAGE_SIZE, &result)); + TEST_CHECK(result == 4096); + OK(uc_query(uc, UC_QUERY_TIMEOUT, &result)); + TEST_CHECK(result == 0); + OK(uc_ctl_get_cpu_model(uc, &model)); + TEST_CHECK(model >= 0 && model < UC_CPU_X86_ENDING); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, + 0, 0)); + + OK(uc_close(uc)); +} + #define TB_COUNT (8) #define TCG_MAX_INSNS (512) // from tcg.h #define CODE_LEN TB_COUNT *TCG_MAX_INSNS @@ -116,12 +825,20 @@ static void test_uc_ctl_change_page_size(void) { uc_engine *uc; uc_engine *uc2; + size_t mode; uint32_t pg = 0; OK(uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc)); OK(uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc2)); OK(uc_ctl_set_page_size(uc, 4096)); + uc_assert_err(UC_ERR_ARG, uc_ctl_set_page_size(uc, 0)); + uc_assert_err(UC_ERR_ARG, uc_ctl_set_page_size(uc, 1536)); + OK(uc_ctl_get_page_size(uc, &pg)); + TEST_CHECK(pg == 4096); + OK(uc_query(uc, UC_QUERY_MODE, &mode)); + TEST_CHECK((mode & UC_MODE_THUMB) == 0); + uc_assert_err(UC_ERR_ARG, uc_ctl_set_page_size(uc, 1024)); OK(uc_ctl_get_page_size(uc, &pg)); TEST_CHECK(pg == 4096); @@ -221,7 +938,7 @@ static void test_uc_ctl_arm_cpu(void) } #endif -static void test_uc_hook_cached_cb(uc_engine *uc, uint64_t addr, size_t size, +static void test_uc_hook_cached_cb(uc_engine *uc, uint64_t addr, uint32_t size, void *user_data) { uint64_t *p = (uint64_t *)user_data; @@ -300,6 +1017,152 @@ static void test_uc_emu_stop_set_ip(void) OK(uc_close(uc)); } +typedef enum TestPcWriteMethod { + TEST_PC_WRITE_SINGLE, + TEST_PC_WRITE_BATCH, + TEST_PC_WRITE_BATCH2, + TEST_PC_WRITE_SIZED, + TEST_PC_WRITE_METHOD_COUNT, +} TestPcWriteMethod; + +typedef struct TestPcWriteData { + TestPcWriteMethod method; + uint32_t count; +} TestPcWriteData; + +static void test_uc_set_ip_callback(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + TestPcWriteData *data = (TestPcWriteData *)user_data; + uint64_t rip = code_start + 0xb; + int regs[] = { UC_X86_REG_RIP }; + void *values[] = { &rip }; + const void *const_values[] = { &rip }; + size_t sizes[] = { sizeof(rip) }; + + if (address != code_start + 0x7) { + return; + } + data->count++; + switch (data->method) { + case TEST_PC_WRITE_SINGLE: + OK(uc_reg_write(uc, UC_X86_REG_RIP, &rip)); + break; + case TEST_PC_WRITE_BATCH: + OK(uc_reg_write_batch(uc, regs, values, 1)); + break; + case TEST_PC_WRITE_BATCH2: + OK(uc_reg_write_batch2(uc, regs, const_values, sizes, 1)); + break; + case TEST_PC_WRITE_SIZED: + OK(uc_reg_write2(uc, UC_X86_REG_RIP, &rip, sizes)); + break; + default: + TEST_CHECK(false); + break; + } +} + +static void test_uc_set_ip_write_apis(void) +{ + const char code[] = + "\x48\x31\xc0" /* xor rax, rax */ + "\x90" /* nop */ + "\x48\xff\xc0" /* inc rax */ + "\x90" /* callback changes RIP */ + "\x48\xff\xc0" /* must not execute */ + "\x90"; /* destination */ + TestPcWriteMethod method; + + for (method = TEST_PC_WRITE_SINGLE; + method < TEST_PC_WRITE_METHOD_COUNT; method++) { + TestPcWriteData data = { .method = method }; + uc_engine *uc; + uc_hook hook; + uint64_t rax; + uint64_t rip; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, code, + sizeof(code) - 1); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, + test_uc_set_ip_callback, &data, 1, 0)); + OK(uc_emu_start(uc, code_start, code_start + 0xb, 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_RAX, &rax)); + OK(uc_reg_read(uc, UC_X86_REG_RIP, &rip)); + TEST_CHECK(rax == 1); + TEST_CHECK(rip == code_start + 0xb); + TEST_CHECK(data.count == 1); + OK(uc_close(uc)); + } +} + +typedef struct TestContextRestoreData { + uc_context *context; + uint32_t count; +} TestContextRestoreData; + +static void test_uc_context_restore_callback(uc_engine *uc, + uint64_t address, + uint32_t size, + void *user_data) +{ + TestContextRestoreData *data = + (TestContextRestoreData *)user_data; + + data->count++; + OK(uc_context_restore(uc, data->context)); +} + +static void test_uc_context_restore_from_callback(void) +{ + const char code[] = { + 0x40, /* inc eax */ + 0x43, /* callback before inc ebx */ + 0x41, /* inc ecx */ + 0x42, /* destination */ + }; + TestContextRestoreData data = { 0 }; + uint32_t destination = (uint32_t)code_start + 3; + uint32_t eax = 0x10; + uint32_t ebx = 0x20; + uint32_t ecx = 0x30; + uint32_t eip; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code)); + OK(uc_context_alloc(uc, &data.context)); + OK(uc_reg_write(uc, UC_X86_REG_EIP, &destination)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &ecx)); + OK(uc_context_save(uc, data.context)); + + eax = 0; + ebx = 0; + ecx = 0; + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &ecx)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, + test_uc_context_restore_callback, &data, + code_start + 1, code_start + 1)); + + OK(uc_emu_start(uc, code_start, destination, 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + TEST_CHECK(data.count == 1); + TEST_CHECK(eip == destination); + TEST_CHECK(eax == 0x10); + TEST_CHECK(ebx == 0x20); + TEST_CHECK(ecx == 0x30); + + OK(uc_context_free(data.context)); + OK(uc_close(uc)); +} + static bool test_tlb_clear_tlb(uc_engine *uc, uint64_t addr, uc_mem_type type, uc_tlb_entry *result, void *user_data) { @@ -397,6 +1260,20 @@ TEST_LIST = { {"test_uc_ctl_arch", test_uc_ctl_arch}, {"test_uc_ctl_time_out", test_uc_ctl_time_out}, {"test_uc_ctl_exits", test_uc_ctl_exits}, + {"test_uc_ctl_exits_boundaries", test_uc_ctl_exits_boundaries}, + {"test_uc_timeout_reuse", test_uc_timeout_reuse}, + {"test_uc_timeout_max_tb", test_uc_timeout_max_tb}, + {"test_uc_reg_sized", test_uc_reg_sized}, + {"test_uc_reg_batch", test_uc_reg_batch}, + {"test_uc_reg_batch_partial_failure", test_uc_reg_batch_partial_failure}, + {"test_uc_context_reg_apis", test_uc_context_reg_apis}, + {"test_uc_context_batch_partial_failure", + test_uc_context_batch_partial_failure}, + {"test_uc_nested_timeout", test_uc_nested_timeout}, + {"test_uc_nested_timeout_completion", + test_uc_nested_timeout_completion}, + {"test_uc_invalid_hook_types", test_uc_invalid_hook_types}, + {"test_uc_query_and_cpu_model", test_uc_query_and_cpu_model}, {"test_uc_ctl_tb_cache", test_uc_ctl_tb_cache}, #ifdef UNICORN_HAS_ARM {"test_uc_ctl_change_page_size", test_uc_ctl_change_page_size}, @@ -409,6 +1286,9 @@ TEST_LIST = { #endif {"test_uc_hook_cached_uaf", test_uc_hook_cached_uaf}, {"test_uc_emu_stop_set_ip", test_uc_emu_stop_set_ip}, + {"test_uc_set_ip_write_apis", test_uc_set_ip_write_apis}, + {"test_uc_context_restore_from_callback", + test_uc_context_restore_from_callback}, {"test_tlb_clear", test_tlb_clear}, {"test_noexec", test_noexec}, {"test_add_block_hook", test_add_block_hook}, diff --git a/tests/unit/test_m68k.c b/tests/unit/test_m68k.c index 9b177a0452..c6ba807e34 100644 --- a/tests/unit/test_m68k.c +++ b/tests/unit/test_m68k.c @@ -5,8 +5,52 @@ const uint64_t code_start = 0x1000; const uint64_t code_len = 0x4000; +#define M68K_FPCR_RND_NEAREST 0x0000 +#define M68K_FPCR_RND_ZERO 0x0010 +#define M68K_FPCR_RND_MINUS 0x0020 +#define M68K_FPCR_RND_PLUS 0x0030 +#define M68K_FPSR_CC_NAN 0x01000000 +#define M68K_FPSR_CC_INFINITY 0x02000000 +#define M68K_FPSR_CC_ZERO 0x04000000 +#define M68K_FPSR_CC_MASK 0x0f000000 +#define M68K_MMU_TCR_ENABLED 0x8000 +#define M68K_MMU_TTR_ALL 0x0000c000 +#define M68K_MMU_DESC_VALID 0x00000001 +#define M68K_MMU_DESC_TABLE 0x00000002 +#define M68K_MMU_DESC_WRITE_PROTECT 0x00000004 +#define M68K_MMUSR_WRITE_PROTECT 0x00000004 +#define M68K_MMUSR_RESIDENT 0x00000001 + +static uint32_t m68k_load_be32(const uint8_t *value) +{ + return ((uint32_t)value[0] << 24) | ((uint32_t)value[1] << 16) | + ((uint32_t)value[2] << 8) | value[3]; +} + +static void m68k_write_be32(uc_engine *uc, uint64_t address, uint32_t value) +{ + uint8_t bytes[] = { + value >> 24, + value >> 16, + value >> 8, + value, + }; + + OK(uc_mem_write(uc, address, bytes, sizeof(bytes))); +} + +static void m68k_write_be64(uc_engine *uc, uint64_t address, uint64_t value) +{ + uint8_t bytes[] = { + value >> 56, value >> 48, value >> 40, value >> 32, + value >> 24, value >> 16, value >> 8, value, + }; + + OK(uc_mem_write(uc, address, bytes, sizeof(bytes))); +} + static void uc_common_setup(uc_engine **uc, uc_arch arch, uc_mode mode, - const char *code, uint64_t size, + const void *code, uint64_t size, uc_cpu_m68k cpu_model) { OK(uc_open(arch, mode, uc)); @@ -73,6 +117,31 @@ static void test_sr_contains_flags(void) OK(uc_close(uc)); } +static void test_m68k_reset_and_lazy_ccr(void) +{ + const uint8_t code[] = { + 0x40, 0xc1, /* move.w sr, d1 */ + 0x70, 0xff, /* moveq #-1, d0 */ + 0x52, 0x00, /* addq.b #1, d0 */ + }; + uc_engine *uc; + uint32_t d1 = UINT32_MAX; + uint32_t sr = 0; + + uc_common_setup(&uc, UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, + (const char *)code, sizeof(code), + UC_CPU_M68K_M68000); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_read(uc, UC_M68K_REG_SR, &sr)); + + TEST_CHECK_(d1 == 0, "reset sr = 0x%08x", d1); + TEST_CHECK_((sr & 0x1f) == 0x15, "lazy ccr = 0x%02x", sr & 0x1f); + + OK(uc_close(uc)); +} + static void test_fetoxm1(void) { uc_engine *uc; @@ -103,6 +172,257 @@ static void test_fetoxm1(void) OK(uc_close(uc)); } +static int32_t run_m68k_fint(uint64_t input, uint32_t fpcr) +{ + const uint8_t code[] = { + 0xf2, 0x00, 0x90, 0x00, /* fmove.l d0, fpcr */ + 0xf2, 0x10, 0x54, 0x00, /* fmove.d (a0), fp0 */ + 0xf2, 0x00, 0x00, 0x81, /* fint.x fp0, fp1 */ + 0xf2, 0x11, 0x60, 0x80, /* fmove.l fp1, (a1) */ + 0xf2, 0x02, 0xb0, 0x00, /* fmove.l fpcr, d2 */ + }; + const uint32_t input_address = code_start + 0x800; + const uint32_t result_address = input_address + 8; + uint8_t result[4]; + uc_engine *uc; + uint32_t observed_fpcr = 0; + uint32_t a0 = input_address; + uint32_t a1 = result_address; + + uc_common_setup(&uc, UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, (const char *)code, + sizeof(code), UC_CPU_M68K_M68020); + m68k_write_be64(uc, input_address, input); + OK(uc_reg_write(uc, UC_M68K_REG_D0, &fpcr)); + OK(uc_reg_write(uc, UC_M68K_REG_A0, &a0)); + OK(uc_reg_write(uc, UC_M68K_REG_A1, &a1)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_mem_read(uc, result_address, result, sizeof(result))); + OK(uc_reg_read(uc, UC_M68K_REG_D2, &observed_fpcr)); + TEST_CHECK_(observed_fpcr == fpcr, "fpcr = 0x%08x", observed_fpcr); + + OK(uc_close(uc)); + return (int32_t)m68k_load_be32(result); +} + +static void test_m68k_fpcr_rounding_modes(void) +{ + static const struct { + uint32_t fpcr; + int32_t positive; + int32_t negative; + } cases[] = { + {M68K_FPCR_RND_NEAREST, 2, -2}, + {M68K_FPCR_RND_ZERO, 1, -1}, + {M68K_FPCR_RND_MINUS, 1, -2}, + {M68K_FPCR_RND_PLUS, 2, -1}, + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + TEST_CHECK_(run_m68k_fint(UINT64_C(0x3ff8000000000000), + cases[i].fpcr) == cases[i].positive, + "positive rounding mode 0x%02x", cases[i].fpcr); + TEST_CHECK_(run_m68k_fint(UINT64_C(0xbff8000000000000), + cases[i].fpcr) == cases[i].negative, + "negative rounding mode 0x%02x", cases[i].fpcr); + } +} + +static void test_m68k_fpsr_exception_condition_codes(void) +{ + const uint8_t code[] = { + 0xf2, 0x10, 0x54, 0x00, /* fmove.d (a0), fp0 */ + 0xf2, 0x00, 0x00, 0x84, /* fsqrt.x fp0, fp1 */ + 0xf2, 0x00, 0xa8, 0x00, /* fmove.l fpsr, d0 */ + 0xf2, 0x11, 0x54, 0x00, /* fmove.d (a1), fp0 */ + 0xf2, 0x12, 0x54, 0x80, /* fmove.d (a2), fp1 */ + 0xf2, 0x00, 0x00, 0xa0, /* fdiv.x fp0, fp1 */ + 0xf2, 0x01, 0xa8, 0x00, /* fmove.l fpsr, d1 */ + }; + const uint32_t data_address = code_start + 0x800; + uc_engine *uc; + uint32_t a0 = data_address; + uint32_t a1 = data_address + 8; + uint32_t a2 = data_address + 16; + uint32_t invalid_fpsr = 0; + uint32_t divzero_fpsr = 0; + + uc_common_setup(&uc, UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, (const char *)code, + sizeof(code), UC_CPU_M68K_M68020); + m68k_write_be64(uc, a0, UINT64_C(0xbff0000000000000)); + m68k_write_be64(uc, a1, UINT64_C(0)); + m68k_write_be64(uc, a2, UINT64_C(0x3ff0000000000000)); + OK(uc_reg_write(uc, UC_M68K_REG_A0, &a0)); + OK(uc_reg_write(uc, UC_M68K_REG_A1, &a1)); + OK(uc_reg_write(uc, UC_M68K_REG_A2, &a2)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &invalid_fpsr)); + OK(uc_reg_read(uc, UC_M68K_REG_D1, &divzero_fpsr)); + + TEST_CHECK_((invalid_fpsr & (M68K_FPSR_CC_NAN | M68K_FPSR_CC_INFINITY | + M68K_FPSR_CC_ZERO)) == M68K_FPSR_CC_NAN, + "invalid fpsr = 0x%08x", invalid_fpsr); + TEST_CHECK_((divzero_fpsr & M68K_FPSR_CC_MASK) == M68K_FPSR_CC_INFINITY, + "divide-by-zero fpsr = 0x%08x", divzero_fpsr); + + OK(uc_close(uc)); +} + +static void setup_m68040_mmu(uc_engine **uc, const uint8_t *code, + size_t code_size, uint32_t page_descriptor) +{ + const uint32_t root_address = 0x2000; + const uint32_t pointer_address = 0x2800; + const uint32_t page_table_address = 0x3000; + const uint32_t physical_page = 0x4000; + const uint32_t page_entry = page_table_address + 0x20; + uint32_t sr = 0x2000; + uint32_t dfc = 1; + uint32_t itt0 = M68K_MMU_TTR_ALL; + uint16_t tcr = M68K_MMU_TCR_ENABLED; + + uc_common_setup(uc, UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, (const char *)code, + code_size, UC_CPU_M68K_M68040); + m68k_write_be32(*uc, root_address, pointer_address | M68K_MMU_DESC_TABLE); + m68k_write_be32(*uc, pointer_address, + page_table_address | M68K_MMU_DESC_TABLE); + m68k_write_be32(*uc, page_entry, physical_page | page_descriptor); + OK(uc_reg_write(*uc, UC_M68K_REG_SR, &sr)); + OK(uc_reg_write(*uc, UC_M68K_REG_CR_DFC, &dfc)); + OK(uc_reg_write(*uc, UC_M68K_REG_CR_SRP, &root_address)); + OK(uc_reg_write(*uc, UC_M68K_REG_CR_URP, &root_address)); + OK(uc_reg_write(*uc, UC_M68K_REG_CR_ITT0, &itt0)); + OK(uc_reg_write(*uc, UC_M68K_REG_CR_TC, &tcr)); +} + +static void test_m68040_ptest_updates_mmusr(void) +{ + const uint8_t code[] = { + 0xf5, 0x68, /* ptestr (a0) */ + }; + const uint32_t logical_address = 0x8000; + uc_engine *uc; + uint32_t a0 = logical_address; + uint32_t mmusr = 0; + + setup_m68040_mmu(&uc, code, sizeof(code), M68K_MMU_DESC_VALID); + OK(uc_reg_write(uc, UC_M68K_REG_A0, &a0)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_M68K_REG_CR_MMUSR, &mmusr)); + TEST_CHECK_(mmusr == (UINT32_C(0x4000) | M68K_MMUSR_RESIDENT), + "mmusr = 0x%08x", mmusr); + + OK(uc_close(uc)); +} + +static void test_m68040_write_protection(void) +{ + const uint8_t ptest_code[] = { + 0xf5, 0x48, /* ptestw (a0) */ + }; + const uint8_t store_code[] = { + 0x10, 0x80, /* move.b d0, (a0) */ + }; + const uint32_t logical_address = 0x8000; + const uint32_t page_descriptor = + M68K_MMU_DESC_VALID | M68K_MMU_DESC_WRITE_PROTECT; + uc_engine *uc; + uint32_t a0 = logical_address; + uint32_t d0 = 0xa5; + uint32_t mmusr = 0; + uint32_t sr = 0; + uint8_t original = 0x5a; + uint8_t result = 0; + + setup_m68040_mmu(&uc, ptest_code, sizeof(ptest_code), page_descriptor); + OK(uc_reg_write(uc, UC_M68K_REG_A0, &a0)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(ptest_code), 0, 0)); + OK(uc_reg_read(uc, UC_M68K_REG_CR_MMUSR, &mmusr)); + TEST_CHECK_(mmusr == (UINT32_C(0x4000) | M68K_MMUSR_RESIDENT | + M68K_MMUSR_WRITE_PROTECT), + "mmusr = 0x%08x", mmusr); + OK(uc_close(uc)); + + setup_m68040_mmu(&uc, store_code, sizeof(store_code), page_descriptor); + OK(uc_mem_write(uc, 0x4000, &original, sizeof(original))); + OK(uc_reg_write(uc, UC_M68K_REG_SR, &sr)); + OK(uc_reg_write(uc, UC_M68K_REG_A0, &a0)); + OK(uc_reg_write(uc, UC_M68K_REG_D0, &d0)); + + uc_assert_err( + UC_ERR_EXCEPTION, + uc_emu_start(uc, code_start, code_start + sizeof(store_code), 0, 0)); + OK(uc_mem_read(uc, 0x4000, &result, sizeof(result))); + TEST_CHECK_(result == original, "protected byte = 0x%02x", result); + + OK(uc_close(uc)); +} + +static void test_m68020_bitfield_crosses_byte_boundary(void) +{ + const uint8_t code[] = { + 0xe9, 0xd0, 0x01, 0x10, /* bfextu (a0){4:16}, d0 */ + 0xeb, 0xd0, 0x11, 0x10, /* bfexts (a0){4:16}, d1 */ + }; + const uint8_t input[] = {0x1a, 0xbc, 0xd6}; + const uint32_t data_address = code_start + 0x800; + uc_engine *uc; + uint32_t a0 = data_address; + uint32_t d0 = 0; + uint32_t d1 = 0; + + uc_common_setup(&uc, UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, (const char *)code, + sizeof(code), UC_CPU_M68K_M68020); + OK(uc_mem_write(uc, data_address, input, sizeof(input))); + OK(uc_reg_write(uc, UC_M68K_REG_A0, &a0)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_M68K_REG_D1, &d1)); + TEST_CHECK_(d0 == 0xabcd, "bfextu = 0x%08x", d0); + TEST_CHECK_(d1 == 0xffffabcd, "bfexts = 0x%08x", d1); + + OK(uc_close(uc)); +} + +static void test_m68020_bitfield_crosses_page_boundary(void) +{ + const uint8_t code[] = { + 0xef, 0xd0, 0x21, 0x10, /* bfins d2, (a0){4:16} */ + 0xe9, 0xd0, 0x01, 0x10, /* bfextu (a0){4:16}, d0 */ + 0xea, 0xd0, 0x01, 0x10, /* bfchg (a0){4:16} */ + 0xe9, 0xd0, 0x11, 0x10, /* bfextu (a0){4:16}, d1 */ + }; + const uint8_t input[] = {0x12, 0x34, 0x56}; + const uint8_t expected[] = {0x15, 0x43, 0x26}; + const uint32_t data_address = 0x1fff; + uint8_t result[sizeof(expected)]; + uc_engine *uc; + uint32_t a0 = data_address; + uint32_t d0 = 0; + uint32_t d1 = 0; + uint32_t d2 = 0xabcd; + + uc_common_setup(&uc, UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, (const char *)code, + sizeof(code), UC_CPU_M68K_M68020); + OK(uc_mem_write(uc, data_address, input, sizeof(input))); + OK(uc_reg_write(uc, UC_M68K_REG_A0, &a0)); + OK(uc_reg_write(uc, UC_M68K_REG_D2, &d2)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_M68K_REG_D1, &d1)); + OK(uc_mem_read(uc, data_address, result, sizeof(result))); + TEST_CHECK_(d0 == 0xabcd, "inserted field = 0x%08x", d0); + TEST_CHECK_(d1 == 0x5432, "changed field = 0x%08x", d1); + TEST_CHECK(memcmp(result, expected, sizeof(expected)) == 0); + + OK(uc_close(uc)); +} + static void test_coldfire_macsr_to_ccr(void) { uc_engine *uc; @@ -475,9 +795,354 @@ static void test_m68010_move_from_sr_privileged(void) OK(uc_close(uc)); } +static void test_m68020_cas_word(void) +{ + const uint8_t code[] = { + 0x0c, 0xd0, 0x00, 0x40, /* cas.w d0, d1, (a0) */ + 0x40, 0xc4, /* move.w sr, d4 */ + }; + const uint8_t compare[] = { 0x12, 0x34 }; + const uint8_t update[] = { 0x56, 0x78 }; + const uint8_t mismatch[] = { 0x20, 0x02 }; + uint8_t result[sizeof(compare)]; + uc_engine *uc; + uint32_t a0 = code_start + 0x1000; + uint32_t d0 = 0xaaaa1234; + uint32_t d1 = 0xbbbb5678; + uint32_t d4; + uint32_t pc = code_start; + uint32_t sr = 0x2710; + + uc_common_setup(&uc, UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, + (const char *)code, sizeof(code), + UC_CPU_M68K_M68020); + OK(uc_mem_write(uc, a0, compare, sizeof(compare))); + OK(uc_reg_write(uc, UC_M68K_REG_A0, &a0)); + OK(uc_reg_write(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_write(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_write(uc, UC_M68K_REG_SR, &sr)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_mem_read(uc, a0, result, sizeof(result))); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_M68K_REG_D4, &d4)); + TEST_CHECK(memcmp(result, update, sizeof(result)) == 0); + TEST_CHECK(d0 == 0xaaaa1234); + TEST_CHECK((d4 & 0x1f) == 0x14); + + d0 = 0xaaaa1001; + sr = 0x2710; + OK(uc_mem_write(uc, a0, mismatch, sizeof(mismatch))); + OK(uc_reg_write(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_write(uc, UC_M68K_REG_PC, &pc)); + OK(uc_reg_write(uc, UC_M68K_REG_SR, &sr)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_mem_read(uc, a0, result, sizeof(result))); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_read(uc, UC_M68K_REG_D4, &d4)); + TEST_CHECK(memcmp(result, mismatch, sizeof(result)) == 0); + TEST_CHECK(d0 == 0xaaaa2002); + TEST_CHECK(d1 == 0xbbbb5678); + TEST_CHECK((d4 & 0x1f) == 0x10); + + OK(uc_close(uc)); +} + +static void test_m68020_cas_long(void) +{ + const uint8_t code[] = { + 0x0e, 0xd0, 0x00, 0x40, /* cas.l d0, d1, (a0) */ + 0x40, 0xc4, /* move.w sr, d4 */ + }; + const uint8_t compare[] = { 0x12, 0x34, 0x56, 0x78 }; + const uint8_t update[] = { 0x9a, 0xbc, 0xde, 0xf0 }; + const uint8_t mismatch[] = { 0x30, 0x00, 0x30, 0x00 }; + uint8_t result[sizeof(compare)]; + uc_engine *uc; + uint32_t a0 = code_start + 0x1000; + uint32_t d0 = 0x12345678; + uint32_t d1 = 0x9abcdef0; + uint32_t d4; + uint32_t pc = code_start; + uint32_t sr = 0x2710; + + uc_common_setup(&uc, UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, + (const char *)code, sizeof(code), + UC_CPU_M68K_M68020); + OK(uc_mem_write(uc, a0, compare, sizeof(compare))); + OK(uc_reg_write(uc, UC_M68K_REG_A0, &a0)); + OK(uc_reg_write(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_write(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_write(uc, UC_M68K_REG_SR, &sr)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_mem_read(uc, a0, result, sizeof(result))); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_M68K_REG_D4, &d4)); + TEST_CHECK(memcmp(result, update, sizeof(result)) == 0); + TEST_CHECK(d0 == 0x12345678); + TEST_CHECK((d4 & 0x1f) == 0x14); + + d0 = 0x20002000; + sr = 0x2710; + OK(uc_mem_write(uc, a0, mismatch, sizeof(mismatch))); + OK(uc_reg_write(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_write(uc, UC_M68K_REG_PC, &pc)); + OK(uc_reg_write(uc, UC_M68K_REG_SR, &sr)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_mem_read(uc, a0, result, sizeof(result))); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_read(uc, UC_M68K_REG_D4, &d4)); + TEST_CHECK(memcmp(result, mismatch, sizeof(result)) == 0); + TEST_CHECK(d0 == 0x30003000); + TEST_CHECK(d1 == 0x9abcdef0); + TEST_CHECK((d4 & 0x1f) == 0x10); + + OK(uc_close(uc)); +} + +static void test_m68020_cas2_word(void) +{ + const uint8_t code[] = { + 0x0c, 0xfc, 0x80, 0x80, 0x90, 0xc1, + /* cas2.w d0:d1, d2:d3, (a0):(a1) */ + 0x40, 0xc4, /* move.w sr, d4 */ + }; + const uint8_t compare1[] = { 0x12, 0x34 }; + const uint8_t compare2[] = { 0x6a, 0xbc }; + const uint8_t update1[] = { 0x56, 0x78 }; + const uint8_t update2[] = { 0x2e, 0xf0 }; + const uint8_t mismatch1[] = { 0x30, 0x03 }; + const uint8_t mismatch2[] = { 0x40, 0x04 }; + uint8_t result1[sizeof(compare1)]; + uint8_t result2[sizeof(compare2)]; + uc_engine *uc; + uint32_t a0 = code_start + 0x1000; + uint32_t a1 = code_start + 0x1100; + uint32_t d0 = 0xaaaa1234; + uint32_t d1 = 0xbbbb6abc; + uint32_t d2 = 0xcccc5678; + uint32_t d3 = 0xdddd2ef0; + uint32_t d4; + uint32_t pc = code_start; + uint32_t sr = 0x2710; + + uc_common_setup(&uc, UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, + (const char *)code, sizeof(code), + UC_CPU_M68K_M68020); + OK(uc_mem_write(uc, a0, compare1, sizeof(compare1))); + OK(uc_mem_write(uc, a1, compare2, sizeof(compare2))); + OK(uc_reg_write(uc, UC_M68K_REG_A0, &a0)); + OK(uc_reg_write(uc, UC_M68K_REG_A1, &a1)); + OK(uc_reg_write(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_write(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_write(uc, UC_M68K_REG_D2, &d2)); + OK(uc_reg_write(uc, UC_M68K_REG_D3, &d3)); + OK(uc_reg_write(uc, UC_M68K_REG_SR, &sr)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_mem_read(uc, a0, result1, sizeof(result1))); + OK(uc_mem_read(uc, a1, result2, sizeof(result2))); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_read(uc, UC_M68K_REG_D4, &d4)); + TEST_CHECK(memcmp(result1, update1, sizeof(result1)) == 0); + TEST_CHECK(memcmp(result2, update2, sizeof(result2)) == 0); + TEST_CHECK(d0 == 0xaaaa1234); + TEST_CHECK(d1 == 0xbbbb6abc); + TEST_CHECK((d4 & 0x1f) == 0x14); + + d0 = 0xaaaa2002; + d1 = 0xbbbb1001; + sr = 0x2710; + OK(uc_mem_write(uc, a0, mismatch1, sizeof(mismatch1))); + OK(uc_mem_write(uc, a1, mismatch2, sizeof(mismatch2))); + OK(uc_reg_write(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_write(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_write(uc, UC_M68K_REG_PC, &pc)); + OK(uc_reg_write(uc, UC_M68K_REG_SR, &sr)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_mem_read(uc, a0, result1, sizeof(result1))); + OK(uc_mem_read(uc, a1, result2, sizeof(result2))); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_read(uc, UC_M68K_REG_D4, &d4)); + TEST_CHECK(memcmp(result1, mismatch1, sizeof(result1)) == 0); + TEST_CHECK(memcmp(result2, mismatch2, sizeof(result2)) == 0); + TEST_CHECK(d0 == 0xaaaa3003); + TEST_CHECK(d1 == 0xbbbb4004); + TEST_CHECK((d4 & 0x1f) == 0x10); + + OK(uc_close(uc)); +} + +static void test_m68020_cas2_long(void) +{ + const uint8_t code[] = { + 0x0e, 0xfc, 0x80, 0x80, 0x90, 0xc1, + /* cas2.l d0:d1, d2:d3, (a0):(a1) */ + 0x40, 0xc4, /* move.w sr, d4 */ + }; + const uint8_t compare1[] = { 0x12, 0x34, 0x56, 0x78 }; + const uint8_t compare2[] = { 0x1a, 0xbc, 0xde, 0xf0 }; + const uint8_t update1[] = { 0x20, 0x00, 0x20, 0x00 }; + const uint8_t update2[] = { 0x21, 0x00, 0x21, 0x00 }; + const uint8_t mismatch1[] = { 0x30, 0x00, 0x30, 0x00 }; + const uint8_t mismatch2[] = { 0x40, 0x00, 0x40, 0x00 }; + uint8_t result1[sizeof(compare1)]; + uint8_t result2[sizeof(compare2)]; + uc_engine *uc; + uint32_t a0 = code_start + 0x1000; + uint32_t a1 = code_start + 0x1100; + uint32_t d0 = 0x12345678; + uint32_t d1 = 0x1abcdef0; + uint32_t d2 = 0x20002000; + uint32_t d3 = 0x21002100; + uint32_t d4; + uint32_t pc = code_start; + uint32_t sr = 0x2710; + + uc_common_setup(&uc, UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, + (const char *)code, sizeof(code), + UC_CPU_M68K_M68020); + OK(uc_mem_write(uc, a0, compare1, sizeof(compare1))); + OK(uc_mem_write(uc, a1, compare2, sizeof(compare2))); + OK(uc_reg_write(uc, UC_M68K_REG_A0, &a0)); + OK(uc_reg_write(uc, UC_M68K_REG_A1, &a1)); + OK(uc_reg_write(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_write(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_write(uc, UC_M68K_REG_D2, &d2)); + OK(uc_reg_write(uc, UC_M68K_REG_D3, &d3)); + OK(uc_reg_write(uc, UC_M68K_REG_SR, &sr)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_mem_read(uc, a0, result1, sizeof(result1))); + OK(uc_mem_read(uc, a1, result2, sizeof(result2))); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_read(uc, UC_M68K_REG_D4, &d4)); + TEST_CHECK(memcmp(result1, update1, sizeof(result1)) == 0); + TEST_CHECK(memcmp(result2, update2, sizeof(result2)) == 0); + TEST_CHECK(d0 == 0x12345678); + TEST_CHECK(d1 == 0x1abcdef0); + TEST_CHECK((d4 & 0x1f) == 0x14); + + d0 = 0x20002000; + d1 = 0x10001000; + sr = 0x2710; + OK(uc_mem_write(uc, a0, mismatch1, sizeof(mismatch1))); + OK(uc_mem_write(uc, a1, mismatch2, sizeof(mismatch2))); + OK(uc_reg_write(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_write(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_write(uc, UC_M68K_REG_PC, &pc)); + OK(uc_reg_write(uc, UC_M68K_REG_SR, &sr)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_mem_read(uc, a0, result1, sizeof(result1))); + OK(uc_mem_read(uc, a1, result2, sizeof(result2))); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_read(uc, UC_M68K_REG_D4, &d4)); + TEST_CHECK(memcmp(result1, mismatch1, sizeof(result1)) == 0); + TEST_CHECK(memcmp(result2, mismatch2, sizeof(result2)) == 0); + TEST_CHECK(d0 == 0x30003000); + TEST_CHECK(d1 == 0x40004000); + TEST_CHECK((d4 & 0x1f) == 0x10); + + OK(uc_close(uc)); +} + +static void test_m68k_fast_count_boundary(void) +{ + const uint8_t code[] = { + 0x70, 0x11, /* moveq #0x11, d0 */ + 0x72, 0x22, /* moveq #0x22, d1 */ + 0x74, 0x33, /* moveq #0x33, d2 */ + }; + uc_engine *uc; + uint32_t d0 = 0; + uint32_t d1 = 0; + uint32_t d2 = 0; + uint32_t pc = 0; + + uc_common_setup(&uc, UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, + (const char *)code, sizeof(code), + UC_CPU_M68K_M68000); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 1)); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_read(uc, UC_M68K_REG_D2, &d2)); + OK(uc_reg_read(uc, UC_M68K_REG_PC, &pc)); + TEST_CHECK_(d0 == 0x11, "d0 = 0x%08x", d0); + TEST_CHECK_(d1 == 0, "d1 = 0x%08x", d1); + TEST_CHECK_(d2 == 0, "d2 = 0x%08x", d2); + TEST_CHECK_(pc == (uint32_t)(code_start + 2), "pc = 0x%08x", pc); + + d0 = 0; + OK(uc_reg_write(uc, UC_M68K_REG_D0, &d0)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 2)); + OK(uc_reg_read(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_M68K_REG_D1, &d1)); + OK(uc_reg_read(uc, UC_M68K_REG_D2, &d2)); + OK(uc_reg_read(uc, UC_M68K_REG_PC, &pc)); + TEST_CHECK_(d0 == 0x11, "d0 = 0x%08x", d0); + TEST_CHECK_(d1 == 0x22, "d1 = 0x%08x", d1); + TEST_CHECK_(d2 == 0, "d2 = 0x%08x", d2); + TEST_CHECK_(pc == (uint32_t)(code_start + 4), "pc = 0x%08x", pc); + + OK(uc_close(uc)); +} + +static void test_m68k_context_roundtrip(void) +{ + uc_engine *uc; + uc_context *context; + uint32_t d0 = 0x11223344; + uint32_t pc = code_start + 0x20; + uint32_t changed = 0; + + OK(uc_open(UC_ARCH_M68K, UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_reg_write(uc, UC_M68K_REG_D0, &d0)); + OK(uc_reg_write(uc, UC_M68K_REG_PC, &pc)); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + OK(uc_reg_write(uc, UC_M68K_REG_D0, &changed)); + OK(uc_reg_write(uc, UC_M68K_REG_PC, &changed)); + OK(uc_context_restore(uc, context)); + + OK(uc_reg_read(uc, UC_M68K_REG_D0, &changed)); + TEST_CHECK(changed == d0); + OK(uc_reg_read(uc, UC_M68K_REG_PC, &changed)); + TEST_CHECK(changed == pc); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + TEST_LIST = {{"test_move_to_sr", test_move_to_sr}, {"test_sr_contains_flags", test_sr_contains_flags}, + {"test_m68k_reset_and_lazy_ccr", + test_m68k_reset_and_lazy_ccr}, {"test_fetoxm1", test_fetoxm1}, + {"test_m68k_fpcr_rounding_modes", + test_m68k_fpcr_rounding_modes}, + {"test_m68k_fpsr_exception_condition_codes", + test_m68k_fpsr_exception_condition_codes}, + {"test_m68040_ptest_updates_mmusr", + test_m68040_ptest_updates_mmusr}, + {"test_m68040_write_protection", + test_m68040_write_protection}, + {"test_m68020_bitfield_crosses_byte_boundary", + test_m68020_bitfield_crosses_byte_boundary}, + {"test_m68020_bitfield_crosses_page_boundary", + test_m68020_bitfield_crosses_page_boundary}, {"test_coldfire_macsr_to_ccr", test_coldfire_macsr_to_ccr}, {"test_ftrapcc_false_consumes_immediate", test_ftrapcc_false_consumes_immediate}, @@ -500,4 +1165,12 @@ TEST_LIST = {{"test_move_to_sr", test_move_to_sr}, {"test_rtr", test_rtr}, {"test_m68010_move_from_sr_privileged", test_m68010_move_from_sr_privileged}, + {"test_m68020_cas_word", test_m68020_cas_word}, + {"test_m68020_cas_long", test_m68020_cas_long}, + {"test_m68020_cas2_word", test_m68020_cas2_word}, + {"test_m68020_cas2_long", test_m68020_cas2_long}, + {"test_m68k_fast_count_boundary", + test_m68k_fast_count_boundary}, + {"test_m68k_context_roundtrip", + test_m68k_context_roundtrip}, {NULL, NULL}}; diff --git a/tests/unit/test_mem.c b/tests/unit/test_mem.c index 963cfc96a0..b943df3cf5 100644 --- a/tests/unit/test_mem.c +++ b/tests/unit/test_mem.c @@ -147,22 +147,273 @@ static void test_mem_protect_map_ptr(void) free(data1); } +static void test_mem_cross_region_access(void) +{ + const uint64_t first_address = 0x1000; + const uint64_t hole_address = 0x4000; + uint8_t input[16]; + uint8_t output[16]; + uint8_t *first = calloc(1, 0x1000); + uint8_t *second = calloc(1, 0x1000); + uint8_t *before_hole = calloc(1, 0x1000); + uc_engine *uc; + size_t i; + + TEST_CHECK(first != NULL); + TEST_CHECK(second != NULL); + TEST_CHECK(before_hole != NULL); + for (i = 0; i < sizeof(input); i++) { + input[i] = (uint8_t)(0x40 + i); + } + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_mem_map_ptr(uc, first_address, 0x1000, UC_PROT_ALL, first)); + OK(uc_mem_map_ptr(uc, first_address + 0x1000, 0x1000, UC_PROT_ALL, second)); + OK(uc_mem_map_ptr(uc, hole_address, 0x1000, UC_PROT_ALL, before_hole)); + + OK(uc_mem_write(uc, first_address + 0xff8, input, sizeof(input))); + TEST_CHECK(memcmp(first + 0xff8, input, 8) == 0); + TEST_CHECK(memcmp(second, input + 8, 8) == 0); + memset(output, 0, sizeof(output)); + OK(uc_mem_read(uc, first_address + 0xff8, output, sizeof(output))); + TEST_CHECK(memcmp(output, input, sizeof(input)) == 0); + + memset(before_hole + 0xff8, 0x5a, 8); + uc_assert_err(UC_ERR_WRITE_UNMAPPED, + uc_mem_write(uc, hole_address + 0xff8, input, sizeof(input))); + for (i = 0; i < 8; i++) { + TEST_CHECK(before_hole[0xff8 + i] == 0x5a); + } + memset(output, 0xa5, sizeof(output)); + uc_assert_err(UC_ERR_READ_UNMAPPED, uc_mem_read(uc, hole_address + 0xff8, + output, sizeof(output))); + for (i = 0; i < sizeof(output); i++) { + TEST_CHECK(output[i] == 0xa5); + } + + OK(uc_close(uc)); + free(before_hole); + free(second); + free(first); +} + +static void test_mem_regions_topology(void) +{ + const uint64_t address = 0x8000; + uint8_t *memory = calloc(1, 0x3000); + uc_mem_region *regions; + uint32_t count; + uc_engine *uc; + + TEST_CHECK(memory != NULL); + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_mem_map_ptr(uc, address, 0x3000, UC_PROT_ALL, memory)); + OK(uc_mem_protect(uc, address + 0x1000, 0x1000, UC_PROT_READ)); + + OK(uc_mem_regions(uc, ®ions, &count)); + TEST_CHECK(count == 3); + TEST_CHECK(regions[0].begin == address); + TEST_CHECK(regions[0].end == address + 0xfff); + TEST_CHECK(regions[0].perms == UC_PROT_ALL); + TEST_CHECK(regions[1].begin == address + 0x1000); + TEST_CHECK(regions[1].end == address + 0x1fff); + TEST_CHECK(regions[1].perms == UC_PROT_READ); + TEST_CHECK(regions[2].begin == address + 0x2000); + TEST_CHECK(regions[2].end == address + 0x2fff); + TEST_CHECK(regions[2].perms == UC_PROT_ALL); + OK(uc_free(regions)); + + OK(uc_mem_unmap(uc, address + 0x1000, 0x1000)); + OK(uc_mem_regions(uc, ®ions, &count)); + TEST_CHECK(count == 2); + TEST_CHECK(regions[0].begin == address); + TEST_CHECK(regions[0].end == address + 0xfff); + TEST_CHECK(regions[1].begin == address + 0x2000); + TEST_CHECK(regions[1].end == address + 0x2fff); + OK(uc_free(regions)); + + OK(uc_close(uc)); + free(memory); +} + static void test_map_at_the_end(void) { + const uint64_t address = UINT64_MAX - 0xfff; uc_engine *uc; uint8_t mem[0x1000]; + uint8_t actual[sizeof(mem)]; + uc_mem_region *regions; + uint32_t count; - memset(mem, 0xff, 0x100); + memset(mem, 0xff, sizeof(mem)); OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); - OK(uc_mem_map(uc, 0xfffffffffffff000, 0x1000, UC_PROT_ALL)); - OK(uc_mem_write(uc, 0xfffffffffffff000, mem, sizeof(mem))); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + uc_assert_err(UC_ERR_MAP, + uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + uc_assert_err(UC_ERR_MAP, + uc_mem_map(uc, address - 0x1000, 0x2000, UC_PROT_ALL)); + + OK(uc_mem_regions(uc, ®ions, &count)); + TEST_CHECK(count == 1); + TEST_CHECK(regions[0].begin == address); + TEST_CHECK(regions[0].end == UINT64_MAX); + TEST_CHECK(regions[0].perms == UC_PROT_ALL); + OK(uc_free(regions)); + + OK(uc_mem_write(uc, address, mem, sizeof(mem))); + OK(uc_mem_read(uc, address, actual, sizeof(actual))); + TEST_CHECK(memcmp(actual, mem, sizeof(actual)) == 0); uc_assert_err(UC_ERR_WRITE_UNMAPPED, uc_mem_write(uc, 0xffffffffffffff00, mem, sizeof(mem))); uc_assert_err(UC_ERR_WRITE_UNMAPPED, uc_mem_write(uc, 0, mem, sizeof(mem))); + OK(uc_mem_unmap(uc, address, 0x1000)); + uc_assert_err(UC_ERR_READ_UNMAPPED, + uc_mem_read(uc, address, actual, sizeof(actual))); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + + OK(uc_close(uc)); +} + +static void test_terminal_two_page_partial_change(bool protect, + bool last_page) +{ + const uint64_t address = UINT64_MAX - 0x1fff; + const uint64_t target = address + (last_page ? 0x1000 : 0); + const uint8_t first_value = 0x41; + const uint8_t last_value = 0x52; + const uint32_t expected_count = protect ? 2 : 1; + uint8_t actual = 0; + uc_mem_region *regions; + uint32_t count; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_mem_map(uc, address, 0x2000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address, &first_value, sizeof(first_value))); + OK(uc_mem_write(uc, address + 0x1000, &last_value, + sizeof(last_value))); + + if (protect) { + OK(uc_mem_protect(uc, target, 0x1000, UC_PROT_READ)); + } else { + OK(uc_mem_unmap(uc, target, 0x1000)); + } + + OK(uc_mem_regions(uc, ®ions, &count)); + if (TEST_CHECK(count == expected_count)) { + if (protect) { + TEST_CHECK(regions[0].begin == address); + TEST_CHECK(regions[0].end == address + 0xfff); + TEST_CHECK(regions[0].perms == + (last_page ? UC_PROT_ALL : UC_PROT_READ)); + TEST_CHECK(regions[1].begin == address + 0x1000); + TEST_CHECK(regions[1].end == UINT64_MAX); + TEST_CHECK(regions[1].perms == + (last_page ? UC_PROT_READ : UC_PROT_ALL)); + } else if (last_page) { + TEST_CHECK(regions[0].begin == address); + TEST_CHECK(regions[0].end == address + 0xfff); + TEST_CHECK(regions[0].perms == UC_PROT_ALL); + } else { + TEST_CHECK(regions[0].begin == address + 0x1000); + TEST_CHECK(regions[0].end == UINT64_MAX); + TEST_CHECK(regions[0].perms == UC_PROT_ALL); + } + } + OK(uc_free(regions)); + + if (protect || last_page) { + OK(uc_mem_read(uc, address, &actual, sizeof(actual))); + TEST_CHECK(actual == first_value); + } else { + uc_assert_err(UC_ERR_READ_UNMAPPED, + uc_mem_read(uc, address, &actual, sizeof(actual))); + } + if (protect || !last_page) { + OK(uc_mem_read(uc, address + 0x1000, &actual, sizeof(actual))); + TEST_CHECK(actual == last_value); + } else { + uc_assert_err( + UC_ERR_READ_UNMAPPED, + uc_mem_read(uc, address + 0x1000, &actual, sizeof(actual))); + } + + OK(uc_close(uc)); +} + +static void test_terminal_two_page_unmap_first(void) +{ + test_terminal_two_page_partial_change(false, false); +} + +static void test_terminal_two_page_unmap_last(void) +{ + test_terminal_two_page_partial_change(false, true); +} + +static void test_terminal_two_page_protect_first(void) +{ + test_terminal_two_page_partial_change(true, false); +} + +static void test_terminal_two_page_protect_last(void) +{ + test_terminal_two_page_partial_change(true, true); +} + +typedef struct TestTerminalPageUnmapData { + uint64_t address; + uint32_t count; +} TestTerminalPageUnmapData; + +static void test_terminal_page_unmap_callback(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + TestTerminalPageUnmapData *data = + (TestTerminalPageUnmapData *)user_data; + + (void)address; + (void)size; + data->count++; + OK(uc_mem_unmap(uc, data->address, 0x1000)); +} + +static void test_terminal_page_active_unmap(void) +{ + const uint64_t address = UINT64_MAX - 0xfff; + const uint64_t code_address = 0x1000; + const uint8_t code[] = { + 0xff, 0xc0, /* inc eax */ + 0xff, 0xc0, /* inc eax */ + }; + TestTerminalPageUnmapData data = {.address = address}; + uint8_t actual = 0; + uint32_t eax = 0; + uc_engine *uc; + uc_hook hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_mem_map(uc, code_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_address, code, sizeof(code))); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, + test_terminal_page_unmap_callback, &data, code_address, + code_address)); + + OK(uc_emu_start(uc, code_address, code_address + sizeof(code), 0, 2)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(data.count == 1); + TEST_CHECK(eax == 2); + uc_assert_err(UC_ERR_READ_UNMAPPED, + uc_mem_read(uc, address, &actual, sizeof(actual))); + + OK(uc_hook_del(uc, hook)); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_close(uc)); } @@ -198,7 +449,7 @@ static void test_map_big_memory(void) } static void test_mem_protect_remove_exec_callback(uc_engine *uc, uint64_t addr, - size_t size, void *data) + uint32_t size, void *data) { uint64_t *p = (uint64_t *)data; (*p)++; @@ -229,6 +480,163 @@ static void test_mem_protect_remove_exec(void) OK(uc_close(uc)); } +typedef enum TestActiveCodePageAction { + TEST_ACTIVE_CODE_PROTECT, + TEST_ACTIVE_CODE_UNMAP, +} TestActiveCodePageAction; + +typedef struct TestActiveCodePageData { + TestActiveCodePageAction action; + uint32_t count; +} TestActiveCodePageData; + +static void test_active_code_page_callback(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + TestActiveCodePageData *data = (TestActiveCodePageData *)user_data; + + data->count++; + if (data->action == TEST_ACTIVE_CODE_PROTECT) { + OK(uc_mem_protect(uc, 0x1000, 0x1000, UC_PROT_READ | UC_PROT_WRITE)); + } else { + OK(uc_mem_unmap(uc, 0x1000, 0x1000)); + } +} + +static void test_active_code_page_change_one(TestActiveCodePageAction action) +{ + const char code[] = "\x40\x40"; + TestActiveCodePageData data = {.action = action}; + uint32_t eax = 0; + uint32_t eip; + uc_engine *uc; + uc_hook hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, 0x1000, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, 0x1000, code, sizeof(code) - 1)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, test_active_code_page_callback, + &data, 0x1000, 0x1000)); + + OK(uc_emu_start(uc, 0x1000, 0x1000 + sizeof(code) - 1, 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + TEST_CHECK(data.count == 1); + TEST_CHECK(eax == 0); + TEST_CHECK(eip == 0x1000); + + if (action == TEST_ACTIVE_CODE_PROTECT) { + uc_assert_err( + UC_ERR_FETCH_PROT, + uc_emu_start(uc, 0x1000, 0x1000 + sizeof(code) - 1, 0, 0)); + } else { + uc_assert_err( + UC_ERR_FETCH_UNMAPPED, + uc_emu_start(uc, 0x1000, 0x1000 + sizeof(code) - 1, 0, 0)); + } + + OK(uc_close(uc)); +} + +static void test_active_code_page_change(void) +{ + test_active_code_page_change_one(TEST_ACTIVE_CODE_PROTECT); + test_active_code_page_change_one(TEST_ACTIVE_CODE_UNMAP); +} + +static void test_inactive_code_page_change_one(TestActiveCodePageAction action) +{ + const char code[] = "\x40"; + uint32_t eax = 0; + uint32_t eip = 0x1000; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, 0x1000, 0x2000, UC_PROT_ALL)); + OK(uc_mem_write(uc, 0x2000, code, sizeof(code) - 1)); + OK(uc_reg_write(uc, UC_X86_REG_EIP, &eip)); + if (action == TEST_ACTIVE_CODE_PROTECT) { + OK(uc_mem_protect(uc, 0x1000, 0x1000, UC_PROT_READ | UC_PROT_WRITE)); + } else { + OK(uc_mem_unmap(uc, 0x1000, 0x1000)); + } + + OK(uc_emu_start(uc, 0x2000, 0x2000 + sizeof(code) - 1, 0, 1)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 1); + + OK(uc_close(uc)); +} + +static void test_inactive_code_page_change(void) +{ + test_inactive_code_page_change_one(TEST_ACTIVE_CODE_PROTECT); + test_inactive_code_page_change_one(TEST_ACTIVE_CODE_UNMAP); +} + +typedef struct TestMmioExecWriteData { + uint64_t ram_address; + uint32_t read_count; + uint32_t code_count; +} TestMmioExecWriteData; + +static uint64_t test_mmio_exec_read_callback(uc_engine *uc, uint64_t offset, + unsigned int size, void *user_data) +{ + TestMmioExecWriteData *data = (TestMmioExecWriteData *)user_data; + uint64_t value = 0; + unsigned int i; + + data->read_count++; + for (i = 0; i < size; i++) { + if (offset + i == 1) { + value |= (uint64_t)0x40 << (i * 8); /* inc eax */ + } + } + return value; +} + +static void test_mmio_exec_write_callback(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + TestMmioExecWriteData *data = (TestMmioExecWriteData *)user_data; + const uint8_t value = 0x5a; + + TEST_CHECK(address == 0x2001); + TEST_CHECK(size == 1); + data->code_count++; + OK(uc_mem_write(uc, data->ram_address, &value, sizeof(value))); +} + +static void test_mmio_exec_write_unrelated_ram(void) +{ + TestMmioExecWriteData data = {.ram_address = 0x1000}; + uint32_t eax = 0; + uint8_t value = 0; + uc_engine *uc; + uc_hook hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, data.ram_address, 0x1000, UC_PROT_ALL)); + OK(uc_mmio_map(uc, 0x2000, 0x1000, test_mmio_exec_read_callback, &data, + NULL, NULL)); + OK(uc_mem_protect(uc, 0x2000, 0x1000, UC_PROT_ALL)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, test_mmio_exec_write_callback, + &data, 0x2001, 0x2001)); + + OK(uc_emu_start(uc, 0x2001, 0x2002, 0, 1)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_mem_read(uc, data.ram_address, &value, sizeof(value))); + TEST_CHECK(data.read_count != 0); + TEST_CHECK(data.code_count == 1); + TEST_CHECK(eax == 1); + TEST_CHECK(value == 0x5a); + + OK(uc_close(uc)); +} + static uint64_t test_mem_protect_mmio_read_cb(struct uc_struct *uc, uint64_t addr, unsigned size, void *user_data) @@ -318,6 +726,241 @@ static void test_snapshot(void) OK(uc_close(uc)); } +typedef struct TestSnapshotCodeRestoreData { + uc_context *context; + uint32_t count; + bool restored; +} TestSnapshotCodeRestoreData; + +static void test_snapshot_code_restore_callback(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + TestSnapshotCodeRestoreData *data = + (TestSnapshotCodeRestoreData *)user_data; + + data->count++; + if (!data->restored) { + data->restored = true; + OK(uc_context_restore(uc, data->context)); + } +} + +static void test_snapshot_code_restore_from_callback(void) +{ + const uint8_t snapshot_code[] = {0x90, 0x43}; + const uint8_t live_code[] = {0x90, 0x40}; + uint8_t restored_code[sizeof(snapshot_code)]; + TestSnapshotCodeRestoreData data = {0}; + uint32_t eax = 0; + uint32_t ebx = 0; + uc_engine *uc; + uc_hook hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, 0x1000, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, 0x1000, snapshot_code, sizeof(snapshot_code))); + OK(uc_context_alloc(uc, &data.context)); + OK(uc_context_save(uc, data.context)); + OK(uc_mem_write(uc, 0x1000, live_code, sizeof(live_code))); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, test_snapshot_code_restore_callback, + &data, 0x1000, 0x1000)); + + OK(uc_emu_start(uc, 0x1000, 0x1000 + sizeof(live_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_mem_read(uc, 0x1000, restored_code, sizeof(restored_code))); + TEST_CHECK(data.count == 2); + TEST_CHECK(eax == 0); + TEST_CHECK(ebx == 1); + TEST_CHECK(memcmp(restored_code, snapshot_code, sizeof(snapshot_code)) == + 0); + + OK(uc_context_free(data.context)); + OK(uc_close(uc)); +} + +typedef struct TestSnapshotCowNestedPatchData { + uint64_t nested_address; + uint64_t cow_address; + uint64_t patch_address; + uint32_t outer_count; + uint32_t inner_count; + bool nested_started; + bool patched; +} TestSnapshotCowNestedPatchData; + +static void test_snapshot_cow_nested_patch_inner(uc_engine *uc, + uint64_t address, + uint32_t size, void *user_data) +{ + TestSnapshotCowNestedPatchData *data = + (TestSnapshotCowNestedPatchData *)user_data; + const uint8_t disjoint_value = 0x5a; + const uint8_t inc_ecx = 0x41; + + data->inner_count++; + if (!data->patched) { + data->patched = true; + OK(uc_mem_write(uc, data->cow_address, &disjoint_value, + sizeof(disjoint_value))); + OK(uc_mem_write(uc, data->patch_address, &inc_ecx, sizeof(inc_ecx))); + } +} + +static void test_snapshot_cow_nested_patch_outer(uc_engine *uc, + uint64_t address, + uint32_t size, void *user_data) +{ + TestSnapshotCowNestedPatchData *data = + (TestSnapshotCowNestedPatchData *)user_data; + + data->outer_count++; + if (!data->nested_started) { + data->nested_started = true; + OK(uc_emu_start(uc, data->nested_address, data->nested_address + 1, 0, + 0)); + } +} + +static void test_snapshot_cow_disjoint_nested_patch(void) +{ + const uint64_t outer_address = 0x1000; + const uint64_t nested_address = 0x3000; + const uint8_t outer_code[] = { + 0x40, /* inc eax */ + 0x40, /* inc eax; patched to inc ecx after COW */ + }; + const uint8_t nested_code[] = {0x42}; /* inc edx */ + TestSnapshotCowNestedPatchData data = { + .nested_address = nested_address, + .cow_address = outer_address + 0x100, + .patch_address = outer_address + 1, + }; + uint32_t eax = 0; + uint32_t ecx = 0; + uint32_t edx = 0; + uc_context *context; + uc_engine *uc; + uc_hook inner_hook; + uc_hook outer_hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, outer_address, 0x3000, UC_PROT_ALL)); + OK(uc_mem_write(uc, outer_address, outer_code, sizeof(outer_code))); + OK(uc_mem_write(uc, nested_address, nested_code, sizeof(nested_code))); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + OK(uc_hook_add(uc, &outer_hook, UC_HOOK_CODE, + test_snapshot_cow_nested_patch_outer, &data, outer_address, + outer_address)); + OK(uc_hook_add(uc, &inner_hook, UC_HOOK_CODE, + test_snapshot_cow_nested_patch_inner, &data, nested_address, + nested_address)); + + OK(uc_emu_start(uc, outer_address, outer_address + sizeof(outer_code), 0, + 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + OK(uc_reg_read(uc, UC_X86_REG_EDX, &edx)); + TEST_CHECK_(data.outer_count == 2, "outer_count=%u", data.outer_count); + TEST_CHECK_(data.inner_count == 1, "inner_count=%u", data.inner_count); + TEST_CHECK_(eax == 1, "eax=0x%x", eax); + TEST_CHECK_(ecx == 1, "ecx=0x%x", ecx); + TEST_CHECK_(edx == 1, "edx=0x%x", edx); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + +typedef struct TestSnapshotNestedRestoreData { + uc_context *context; + uint64_t nested_address; + uint32_t outer_count; + bool nested_started; + bool restored; +} TestSnapshotNestedRestoreData; + +static void test_snapshot_nested_restore_inner(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + TestSnapshotNestedRestoreData *data = + (TestSnapshotNestedRestoreData *)user_data; + + if (!data->restored) { + data->restored = true; + OK(uc_context_restore(uc, data->context)); + } +} + +static void test_snapshot_nested_restore_outer(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + TestSnapshotNestedRestoreData *data = + (TestSnapshotNestedRestoreData *)user_data; + + data->outer_count++; + if (!data->nested_started) { + data->nested_started = true; + OK(uc_emu_start(uc, data->nested_address, data->nested_address + 1, 0, + 0)); + } +} + +static void test_snapshot_nested_restore_outer_instruction(void) +{ + const uint64_t outer_address = 0x1000; + const uint64_t nested_address = 0x3000; + const uint8_t snapshot_code[] = { + 0x40, /* inc eax */ + 0x41, /* inc ecx */ + }; + const uint8_t live_code[] = { + 0x40, /* inc eax */ + 0x40, /* inc eax; restored to inc ecx */ + }; + const uint8_t nested_code[] = {0x90}; /* nop */ + TestSnapshotNestedRestoreData data = { + .nested_address = nested_address, + }; + uint8_t restored_code[sizeof(snapshot_code)]; + uint32_t eax = 0; + uint32_t ecx = 0; + uc_engine *uc; + uc_hook inner_hook; + uc_hook outer_hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, outer_address, 0x3000, UC_PROT_ALL)); + OK(uc_mem_write(uc, outer_address, snapshot_code, sizeof(snapshot_code))); + OK(uc_mem_write(uc, nested_address, nested_code, sizeof(nested_code))); + OK(uc_context_alloc(uc, &data.context)); + OK(uc_context_save(uc, data.context)); + OK(uc_mem_write(uc, outer_address, live_code, sizeof(live_code))); + OK(uc_hook_add(uc, &outer_hook, UC_HOOK_CODE, + test_snapshot_nested_restore_outer, &data, outer_address, + outer_address)); + OK(uc_hook_add(uc, &inner_hook, UC_HOOK_CODE, + test_snapshot_nested_restore_inner, &data, nested_address, + nested_address)); + + OK(uc_emu_start(uc, outer_address, outer_address + sizeof(live_code), 0, + 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + OK(uc_mem_read(uc, outer_address, restored_code, sizeof(restored_code))); + TEST_CHECK_(data.outer_count == 2, "outer_count=%u", data.outer_count); + TEST_CHECK_(eax == 1, "eax=0x%x", eax); + TEST_CHECK_(ecx == 1, "ecx=0x%x", ecx); + TEST_CHECK(memcmp(restored_code, snapshot_code, sizeof(snapshot_code)) == + 0); + + OK(uc_context_free(data.context)); + OK(uc_close(uc)); +} + static bool test_snapshot_with_vtlb_callback(uc_engine *uc, uint64_t addr, uc_mem_type type, uc_tlb_entry *result, @@ -333,6 +976,7 @@ static void test_snapshot_with_vtlb(void) uc_engine *uc; uc_context *c0, *c1; uint32_t mem; + uint8_t code_data; uc_hook hook; // mov eax, [0x2020]; inc eax; mov [0x2020], eax @@ -367,14 +1011,25 @@ static void test_snapshot_with_vtlb(void) 0x400000000 + 0x1000 + sizeof(code) - 1, 0, 0)); OK(uc_mem_read(uc, 0x2020, &mem, sizeof(mem))); TEST_CHECK(LEINT32(mem) == 2); + mem = LEINT32(0xdeadbeef); + OK(uc_mem_write(uc, 0x2020, &mem, sizeof(mem))); + OK(uc_mem_read(uc, 0x2020, &mem, sizeof(mem))); + TEST_CHECK(LEINT32(mem) == 0xdeadbeef); OK(uc_context_restore(uc, c1)); - // TODO check mem OK(uc_mem_read(uc, 0x2020, &mem, sizeof(mem))); TEST_CHECK(LEINT32(mem) == 1); + OK(uc_mem_read(uc, 0x1000, &code_data, sizeof(code_data))); + TEST_CHECK(code_data == 0xa1); + + mem = LEINT32(0xcafebabe); + OK(uc_mem_write(uc, 0x2020, &mem, sizeof(mem))); + OK(uc_mem_read(uc, 0x2020, &mem, sizeof(mem))); + TEST_CHECK(LEINT32(mem) == 0xcafebabe); OK(uc_context_restore(uc, c0)); OK(uc_mem_read(uc, 0x2020, &mem, sizeof(mem))); TEST_CHECK(LEINT32(mem) == 0); - // TODO check mem + OK(uc_mem_read(uc, 0x1000, &code_data, sizeof(code_data))); + TEST_CHECK(code_data == 0xa1); OK(uc_context_free(c0)); OK(uc_context_free(c1)); @@ -413,6 +1068,47 @@ static void test_context_snapshot(void) OK(uc_close(uc)); } +static void test_context_cpu_resave_preserves_memory_snapshot(void) +{ + const uint64_t address = 0x12000; + const uint8_t original = 0x11; + const uint8_t saved = 0x22; + const uint8_t changed = 0x33; + uint8_t actual = 0; + uc_context *base_context; + uc_context *saved_context; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address, &original, sizeof(original))); + OK(uc_context_alloc(uc, &base_context)); + OK(uc_context_alloc(uc, &saved_context)); + OK(uc_context_save(uc, base_context)); + + OK(uc_mem_write(uc, address, &saved, sizeof(saved))); + OK(uc_context_save(uc, saved_context)); + OK(uc_context_restore(uc, base_context)); + + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_CPU)); + OK(uc_context_save(uc, saved_context)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_context_restore(uc, saved_context)); + OK(uc_mem_read(uc, address, &actual, sizeof(actual))); + TEST_CHECK(actual == saved); + + OK(uc_mem_write(uc, address, &changed, sizeof(changed))); + OK(uc_context_restore(uc, base_context)); + OK(uc_context_restore(uc, saved_context)); + OK(uc_mem_read(uc, address, &actual, sizeof(actual))); + TEST_CHECK(actual == saved); + + OK(uc_context_free(base_context)); + OK(uc_context_free(saved_context)); + OK(uc_close(uc)); +} + static void test_snapshot_unmap(void) { uc_engine *uc; @@ -448,6 +1144,667 @@ static void test_snapshot_unmap(void) OK(uc_close(uc)); } +static void test_snapshot_check_regions(uc_engine *uc, + const uc_mem_region *expected, + uint32_t expected_count) +{ + uc_mem_region *regions; + uint32_t count; + uint32_t i; + + OK(uc_mem_regions(uc, ®ions, &count)); + TEST_CHECK(count == expected_count); + for (i = 0; i < count && i < expected_count; i++) { + TEST_CHECK(regions[i].begin == expected[i].begin); + TEST_CHECK(regions[i].end == expected[i].end); + TEST_CHECK(regions[i].perms == expected[i].perms); + } + OK(uc_free(regions)); +} + +static void test_terminal_page_snapshot_restore(void) +{ + const uint64_t address = UINT64_MAX - 0xfff; + const uint8_t original = 0x41; + const uint8_t changed = 0x52; + const uint8_t replacement = 0x63; + const uc_mem_region expected_region = { + address, + UINT64_MAX, + UC_PROT_ALL, + }; + uint8_t actual = 0; + uc_context *context; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address, &original, sizeof(original))); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + OK(uc_mem_write(uc, address, &changed, sizeof(changed))); + OK(uc_mem_read(uc, address, &actual, sizeof(actual))); + TEST_CHECK(actual == changed); + OK(uc_mem_unmap(uc, address, 0x1000)); + uc_assert_err(UC_ERR_READ_UNMAPPED, + uc_mem_read(uc, address, &actual, sizeof(actual))); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address, &replacement, sizeof(replacement))); + + OK(uc_context_restore(uc, context)); + test_snapshot_check_regions(uc, &expected_region, 1); + OK(uc_mem_read(uc, address, &actual, sizeof(actual))); + TEST_CHECK(actual == original); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + +static void test_snapshot_restore_skips_later_unmapped_regions(void) +{ + const uint64_t address = 0x30000; + const uint8_t original = 0x11; + const uint8_t replacement = 0x22; + const uc_mem_region original_region = { + address, + address + 0xfff, + UC_PROT_READ | UC_PROT_WRITE, + }; + const uc_mem_region current_region = { + address, + address + 0xfff, + UC_PROT_READ, + }; + uint8_t actual = 0; + uc_context *context; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, address, 0x1000, original_region.perms)); + OK(uc_mem_write(uc, address, &original, sizeof(original))); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + OK(uc_mem_unmap(uc, address, 0x1000)); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address, &replacement, sizeof(replacement))); + OK(uc_mem_unmap(uc, address, 0x1000)); + OK(uc_mem_map(uc, address, 0x1000, current_region.perms)); + test_snapshot_check_regions(uc, ¤t_region, 1); + + OK(uc_context_restore(uc, context)); + test_snapshot_check_regions(uc, &original_region, 1); + OK(uc_mem_read(uc, address, &actual, sizeof(actual))); + TEST_CHECK(actual == original); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + +static void test_snapshot_empty_flatview(void) +{ + const uint64_t address = 0x40000; + uc_context *context; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_context_restore(uc, context)); + test_snapshot_check_regions(uc, NULL, 0); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + +static void test_snapshot_replace_with_ram(void) +{ + const uint64_t address = 0x10000; + const uint8_t original_first[] = {0x10, 0x11, 0x12, 0x13}; + const uint8_t original_second[] = {0x20, 0x21, 0x22, 0x23}; + const uint8_t replacement[] = {0xa0, 0xa1, 0xa2, 0xa3}; + const uc_mem_region original_regions[] = { + {address, address + 0xfff, UC_PROT_READ | UC_PROT_WRITE}, + {address + 0x1000, address + 0x1fff, UC_PROT_READ | UC_PROT_EXEC}, + }; + const uc_mem_region replacement_regions[] = { + {address, address + 0x1fff, UC_PROT_ALL}, + }; + uint8_t actual[sizeof(original_first)]; + uc_context *older_context; + uc_context *newer_context; + uc_engine *uc; + uc_err err; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_READ | UC_PROT_WRITE)); + OK(uc_mem_map(uc, address + 0x1000, 0x1000, UC_PROT_READ | UC_PROT_EXEC)); + OK(uc_mem_write(uc, address + 0x20, original_first, + sizeof(original_first))); + OK(uc_mem_write(uc, address + 0x1020, original_second, + sizeof(original_second))); + test_snapshot_check_regions(uc, original_regions, + sizeof(original_regions) / + sizeof(original_regions[0])); + + OK(uc_context_alloc(uc, &older_context)); + OK(uc_context_alloc(uc, &newer_context)); + OK(uc_context_save(uc, older_context)); + OK(uc_mem_unmap(uc, address, 0x1000)); + OK(uc_mem_unmap(uc, address + 0x1000, 0x1000)); + OK(uc_mem_map(uc, address, 0x2000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address + 0x20, replacement, sizeof(replacement))); + OK(uc_mem_write(uc, address + 0x1020, replacement, sizeof(replacement))); + + test_snapshot_check_regions(uc, replacement_regions, + sizeof(replacement_regions) / + sizeof(replacement_regions[0])); + OK(uc_context_save(uc, newer_context)); + + err = uc_context_restore(uc, older_context); + if (!TEST_CHECK(err == UC_ERR_OK)) { + TEST_MSG("%s", uc_strerror(err)); + goto cleanup; + } + test_snapshot_check_regions(uc, original_regions, + sizeof(original_regions) / + sizeof(original_regions[0])); + OK(uc_mem_read(uc, address + 0x20, actual, sizeof(actual))); + TEST_CHECK(memcmp(actual, original_first, sizeof(actual)) == 0); + OK(uc_mem_read(uc, address + 0x1020, actual, sizeof(actual))); + TEST_CHECK(memcmp(actual, original_second, sizeof(actual)) == 0); + +cleanup: + OK(uc_context_free(older_context)); + OK(uc_context_free(newer_context)); + OK(uc_close(uc)); +} + +static void test_snapshot_cow_coordinates_and_remap(void) +{ + const uint64_t address = 0x50000; + const uint64_t size = 0x3000; + const uint64_t value_address = address + 0x1020; + const uint8_t original = 0x11; + const uint8_t changed = 0x22; + const uint8_t replacement = 0x33; + const uc_mem_region expected_region = { + address, + address + size - 1, + UC_PROT_ALL, + }; + uint8_t actual = 0; + uc_context *context; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, address, size, UC_PROT_ALL)); + OK(uc_mem_write(uc, value_address, &original, sizeof(original))); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + OK(uc_mem_write(uc, value_address, &changed, sizeof(changed))); + test_snapshot_check_regions(uc, &expected_region, 1); + OK(uc_mem_unmap(uc, address, size)); + OK(uc_mem_map(uc, address, size, UC_PROT_ALL)); + OK(uc_mem_write(uc, value_address, &replacement, sizeof(replacement))); + OK(uc_mem_read(uc, value_address, &actual, sizeof(actual))); + TEST_CHECK(actual == replacement); + + OK(uc_context_restore(uc, context)); + test_snapshot_check_regions(uc, &expected_region, 1); + OK(uc_mem_read(uc, value_address, &actual, sizeof(actual))); + TEST_CHECK(actual == original); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + +static void test_snapshot_cow_replacement_restore(void) +{ + const uint64_t address = 0x54000; + const uint64_t value_address = address + 0x20; + const uint8_t original = 0x41; + const uint8_t cow_value = 0x52; + const uint8_t replacement = 0x63; + uint8_t actual = 0; + uc_context *original_context; + uc_context *cow_context; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, value_address, &original, sizeof(original))); + OK(uc_context_alloc(uc, &original_context)); + OK(uc_context_alloc(uc, &cow_context)); + OK(uc_context_save(uc, original_context)); + + OK(uc_mem_write(uc, value_address, &cow_value, sizeof(cow_value))); + OK(uc_context_save(uc, cow_context)); + OK(uc_mem_unmap(uc, address, 0x1000)); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, value_address, &replacement, sizeof(replacement))); + + OK(uc_context_restore(uc, cow_context)); + OK(uc_mem_read(uc, value_address, &actual, sizeof(actual))); + TEST_CHECK(actual == cow_value); + OK(uc_context_restore(uc, original_context)); + OK(uc_mem_read(uc, value_address, &actual, sizeof(actual))); + TEST_CHECK(actual == original); + OK(uc_context_restore(uc, cow_context)); + OK(uc_mem_read(uc, value_address, &actual, sizeof(actual))); + TEST_CHECK(actual == cow_value); + + OK(uc_context_free(original_context)); + OK(uc_context_free(cow_context)); + OK(uc_close(uc)); +} + +static void test_snapshot_forward_restore_same_level_aba(void) +{ + const uint64_t address = 0x58000; + const uint8_t value_a = 0x71; + const uint8_t value_b = 0x82; + const uint8_t value_c = 0x93; + uint8_t actual = 0; + uc_context *context_a; + uc_context *context_b; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address, &value_a, sizeof(value_a))); + OK(uc_context_alloc(uc, &context_a)); + OK(uc_context_alloc(uc, &context_b)); + OK(uc_context_save(uc, context_a)); + + OK(uc_mem_unmap(uc, address, 0x1000)); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address, &value_b, sizeof(value_b))); + OK(uc_context_save(uc, context_b)); + + OK(uc_context_restore(uc, context_a)); + OK(uc_mem_read(uc, address, &actual, sizeof(actual))); + TEST_CHECK(actual == value_a); + OK(uc_mem_unmap(uc, address, 0x1000)); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address, &value_c, sizeof(value_c))); + + OK(uc_context_restore(uc, context_b)); + OK(uc_mem_read(uc, address, &actual, sizeof(actual))); + TEST_CHECK(actual == value_b); + OK(uc_context_restore(uc, context_a)); + OK(uc_mem_read(uc, address, &actual, sizeof(actual))); + TEST_CHECK(actual == value_a); + OK(uc_context_restore(uc, context_b)); + OK(uc_mem_read(uc, address, &actual, sizeof(actual))); + TEST_CHECK(actual == value_b); + + OK(uc_context_free(context_a)); + OK(uc_context_free(context_b)); + OK(uc_close(uc)); +} + +static void test_snapshot_equal_priority_cow_leaves(void) +{ + const uint64_t address = 0x5c000; + const uint8_t original[] = {0x11, 0x22}; + const uint8_t changed[] = {0xa1, 0xb2}; + const uint64_t offsets[] = {0x20, 0x1020}; + uint8_t actual; + uc_context *base_context; + uc_context *cow_context; + uc_engine *uc; + size_t i; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, address, 0x3000, UC_PROT_ALL)); + for (i = 0; i < 2; i++) { + OK(uc_mem_write(uc, address + offsets[i], &original[i], 1)); + } + OK(uc_context_alloc(uc, &base_context)); + OK(uc_context_alloc(uc, &cow_context)); + OK(uc_context_save(uc, base_context)); + + for (i = 0; i < 2; i++) { + OK(uc_mem_write(uc, address + offsets[i], &changed[i], 1)); + } + OK(uc_context_save(uc, cow_context)); + + OK(uc_context_restore(uc, base_context)); + for (i = 0; i < 2; i++) { + OK(uc_mem_read(uc, address + offsets[i], &actual, 1)); + TEST_CHECK(actual == original[i]); + } + OK(uc_context_restore(uc, cow_context)); + for (i = 0; i < 2; i++) { + OK(uc_mem_read(uc, address + offsets[i], &actual, 1)); + TEST_CHECK(actual == changed[i]); + } + + OK(uc_context_free(base_context)); + OK(uc_context_free(cow_context)); + OK(uc_close(uc)); +} + +static void test_snapshot_repeated_root_leaf_forward_restore(void) +{ + const uint64_t address = 0x60000; + const uint8_t original = 0x31; + const uint8_t changed = 0x42; + uint8_t actual; + uc_context *leaf_context; + uc_context *cow_context; + uc_engine *uc; + unsigned int i; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address, &original, 1)); + OK(uc_context_alloc(uc, &leaf_context)); + OK(uc_context_alloc(uc, &cow_context)); + OK(uc_context_save(uc, leaf_context)); + OK(uc_mem_write(uc, address, &changed, 1)); + OK(uc_context_save(uc, cow_context)); + + for (i = 0; i < 32; i++) { + OK(uc_context_restore(uc, leaf_context)); + OK(uc_mem_read(uc, address, &actual, 1)); + TEST_CHECK(actual == original); + OK(uc_context_restore(uc, cow_context)); + OK(uc_mem_read(uc, address, &actual, 1)); + TEST_CHECK(actual == changed); + } + + OK(uc_context_free(leaf_context)); + OK(uc_context_free(cow_context)); + OK(uc_close(uc)); +} + +static void test_snapshot_context_resave_free_ownership(void) +{ + const uint64_t old_address = 0x64000; + const uint64_t new_address = 0x68000; + const uint8_t old_value = 0x51; + const uint8_t new_value = 0x62; + uint8_t actual; + uc_context *context; + uc_engine *uc; + unsigned int i; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, old_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, old_address, &old_value, 1)); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + OK(uc_mem_unmap(uc, old_address, 0x1000)); + + OK(uc_mem_map(uc, new_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, new_address, &new_value, 1)); + OK(uc_context_save(uc, context)); + OK(uc_mem_unmap(uc, new_address, 0x1000)); + OK(uc_context_restore(uc, context)); + uc_assert_err(UC_ERR_READ_UNMAPPED, + uc_mem_read(uc, old_address, &actual, 1)); + OK(uc_mem_read(uc, new_address, &actual, 1)); + TEST_CHECK(actual == new_value); + + OK(uc_mem_unmap(uc, new_address, 0x1000)); + OK(uc_context_free(context)); + for (i = 0; i < 64; i++) { + OK(uc_mem_map(uc, new_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_unmap(uc, new_address, 0x1000)); + } + OK(uc_close(uc)); +} + +static void test_snapshot_final_release_restores_memory_apis(void) +{ + const uint64_t address = 0x7c000; + const uint8_t value = 0x5a; + uint8_t actual = 0; + uc_context *context; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, address, 0x2000, UC_PROT_ALL)); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + OK(uc_mem_write(uc, address, &value, sizeof(value))); + + OK(uc_context_free(context)); + OK(uc_mem_read(uc, address, &actual, sizeof(actual))); + TEST_CHECK(actual == value); + OK(uc_mem_protect(uc, address, 0x1000, UC_PROT_READ)); + OK(uc_mem_unmap(uc, address + 0x1000, 0x1000)); + OK(uc_mem_unmap(uc, address, 0x1000)); + + OK(uc_close(uc)); +} + +static void test_snapshot_close_with_retained_mappings(void) +{ + const uint64_t ram_address = 0x6c000; + const uint64_t host_address = 0x70000; + const uint64_t mmio_address = 0x74000; + uint8_t *host_memory = calloc(1, 0x1000); + uc_context *context; + uc_engine *uc; + + TEST_ASSERT(host_memory != NULL); + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, ram_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_map_ptr(uc, host_address, 0x1000, UC_PROT_ALL, host_memory)); + OK(uc_mmio_map(uc, mmio_address, 0x1000, NULL, NULL, NULL, NULL)); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + OK(uc_mem_unmap(uc, ram_address, 0x1000)); + OK(uc_mem_unmap(uc, host_address, 0x1000)); + OK(uc_mem_unmap(uc, mmio_address, 0x1000)); + OK(uc_close(uc)); + OK(uc_context_free(context)); + free(host_memory); +} + +static void test_snapshot_replace_with_host_memory(void) +{ + const uint64_t address = 0x18000; + const uint8_t original[] = {0x51, 0x52, 0x53, 0x54}; + const uint8_t replacement[] = {0xa1, 0xa2, 0xa3, 0xa4}; + const uc_mem_region original_region = { + address, + address + 0xfff, + UC_PROT_READ | UC_PROT_WRITE, + }; + uint8_t actual[sizeof(original)]; + uint8_t *host_memory = calloc(1, 0x1000); + uc_context *context; + uc_engine *uc; + + TEST_ASSERT(host_memory != NULL); + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, address, 0x1000, original_region.perms)); + OK(uc_mem_write(uc, address + 0x20, original, sizeof(original))); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + OK(uc_mem_unmap(uc, address, 0x1000)); + memcpy(host_memory + 0x20, replacement, sizeof(replacement)); + OK(uc_mem_map_ptr(uc, address, 0x1000, UC_PROT_ALL, host_memory)); + OK(uc_mem_read(uc, address + 0x20, actual, sizeof(actual))); + TEST_CHECK(memcmp(actual, replacement, sizeof(actual)) == 0); + + OK(uc_context_restore(uc, context)); + test_snapshot_check_regions(uc, &original_region, 1); + OK(uc_mem_read(uc, address + 0x20, actual, sizeof(actual))); + TEST_CHECK(memcmp(actual, original, sizeof(actual)) == 0); + TEST_CHECK(memcmp(host_memory + 0x20, replacement, sizeof(replacement)) == + 0); + + OK(uc_context_free(context)); + OK(uc_close(uc)); + free(host_memory); +} + +typedef struct TestSnapshotMmioData { + uint64_t read_value; + uint64_t write_value; + uint64_t read_offset; + uint64_t write_offset; + unsigned int read_size; + unsigned int write_size; + uint32_t read_count; + uint32_t write_count; +} TestSnapshotMmioData; + +static uint64_t test_snapshot_mmio_read(uc_engine *uc, uint64_t offset, + unsigned int size, void *user_data) +{ + TestSnapshotMmioData *data = (TestSnapshotMmioData *)user_data; + + (void)uc; + data->read_count++; + data->read_offset = offset; + data->read_size = size; + return data->read_value; +} + +static void test_snapshot_mmio_write(uc_engine *uc, uint64_t offset, + unsigned int size, uint64_t value, + void *user_data) +{ + TestSnapshotMmioData *data = (TestSnapshotMmioData *)user_data; + + (void)uc; + data->write_count++; + data->write_offset = offset; + data->write_size = size; + data->write_value = value; +} + +static void test_snapshot_existing_mmio_write(void) +{ + const uint64_t address = 0x20000; + const uint32_t value = LEINT32(0x76543210); + TestSnapshotMmioData data = {0}; + uc_context *context; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mmio_map(uc, address, 0x1000, NULL, NULL, + test_snapshot_mmio_write, &data)); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + OK(uc_mem_write(uc, address + 0x20, &value, sizeof(value))); + TEST_CHECK(data.write_count == 1); + TEST_CHECK(data.write_offset == 0x20); + TEST_CHECK(data.write_size == sizeof(value)); + TEST_CHECK(data.write_value == 0x76543210); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + +static void test_snapshot_replace_with_mmio(void) +{ + const uint64_t address = 0x20000; + const uint8_t original_first[] = {0x30, 0x31, 0x32, 0x33}; + const uint8_t original_second[] = {0x40, 0x41, 0x42, 0x43}; + /* mov rax, 0x21020; mov dword ptr [rax], 0x76543210 */ + const uint8_t mmio_write_code[] = { + 0x48, 0xc7, 0xc0, 0x20, 0x10, 0x02, 0x00, + 0xc7, 0x00, 0x10, 0x32, 0x54, 0x76, + }; + const uc_mem_region original_regions[] = { + {0x1000, 0x1fff, UC_PROT_ALL}, + {address, address + 0xfff, UC_PROT_READ | UC_PROT_WRITE}, + {address + 0x1000, address + 0x1fff, UC_PROT_READ | UC_PROT_EXEC}, + }; + TestSnapshotMmioData data = {.read_value = 0x89abcdef}; + uint8_t actual[sizeof(original_first)]; + uint32_t mmio_value = 0; + uint32_t write_value; + uc_context *context; + uc_engine *uc; + uc_err err; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_context_mode(uc, UC_CTL_CONTEXT_MEMORY)); + OK(uc_mem_map(uc, address, 0x1000, UC_PROT_READ | UC_PROT_WRITE)); + OK(uc_mem_map(uc, address + 0x1000, 0x1000, UC_PROT_READ | UC_PROT_EXEC)); + OK(uc_mem_write(uc, address + 0x20, original_first, + sizeof(original_first))); + OK(uc_mem_write(uc, address + 0x1020, original_second, + sizeof(original_second))); + OK(uc_mem_map(uc, 0x1000, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, 0x1000, mmio_write_code, sizeof(mmio_write_code))); + test_snapshot_check_regions(uc, original_regions, + sizeof(original_regions) / + sizeof(original_regions[0])); + + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + OK(uc_mem_unmap(uc, address, 0x1000)); + OK(uc_mem_unmap(uc, address + 0x1000, 0x1000)); + OK(uc_mmio_map(uc, address, 0x2000, test_snapshot_mmio_read, &data, + test_snapshot_mmio_write, &data)); + + OK(uc_mem_read(uc, address + 0x20, &mmio_value, sizeof(mmio_value))); + TEST_CHECK(LEINT32(mmio_value) == 0x89abcdef); + TEST_CHECK(data.read_count == 1); + TEST_CHECK(data.read_offset == 0x20); + TEST_CHECK(data.read_size == sizeof(mmio_value)); + OK(uc_emu_start(uc, 0x1000, 0x1000 + sizeof(mmio_write_code), 0, 0)); + TEST_CHECK(data.write_count == 1); + TEST_CHECK(data.write_offset == 0x1020); + TEST_CHECK(data.write_size == sizeof(uint32_t)); + TEST_CHECK(data.write_value == 0x76543210); + + err = uc_context_restore(uc, context); + if (!TEST_CHECK(err == UC_ERR_OK)) { + TEST_MSG("%s", uc_strerror(err)); + goto cleanup; + } + test_snapshot_check_regions(uc, original_regions, + sizeof(original_regions) / + sizeof(original_regions[0])); + OK(uc_mem_read(uc, address + 0x20, actual, sizeof(actual))); + TEST_CHECK(memcmp(actual, original_first, sizeof(actual)) == 0); + OK(uc_mem_read(uc, address + 0x1020, actual, sizeof(actual))); + TEST_CHECK(memcmp(actual, original_second, sizeof(actual)) == 0); + TEST_CHECK(data.read_count == 1); + + write_value = LEINT32(0x12345678); + OK(uc_mem_write(uc, address + 0x20, &write_value, sizeof(write_value))); + OK(uc_mem_read(uc, address + 0x20, &mmio_value, sizeof(mmio_value))); + TEST_CHECK(LEINT32(mmio_value) == 0x12345678); + TEST_CHECK(data.read_count == 1); + TEST_CHECK(data.write_count == 1); + +cleanup: + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + static void parts_increment(size_t idx, char parts[3]) { if (idx && idx % 3 == 0) { @@ -504,12 +1861,12 @@ static void test_mem_read_and_write_large_memory_block(void) } // Android CI/CD services do not have enough memory capacity for this // test to work. Executing it will result in a permanent loop with the - // low memory killer daemon. + // low memory killer daemon. #ifdef __ANDROID__ return; -#endif +#endif - OK(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc)); + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); OK(uc_mem_map(uc, mem_addr, mem_size, UC_PROT_ALL)); pmem = malloc(mem_size); @@ -528,7 +1885,7 @@ static void test_mem_read_and_write_large_memory_block(void) } static bool test_v2p_tlb_fill(uc_engine *uc, uint64_t addr, uc_mem_type type, - uc_tlb_entry *result, void *user_data) + uc_tlb_entry *result, void *user_data) { if (type != UC_MEM_READ) return false; @@ -553,8 +1910,9 @@ static void test_virtual_to_physical(void) OK(uc_close(uc)); } -static bool test_virtual_write_tlb_fill(uc_engine *uc, uint64_t addr, uc_mem_type type, - uc_tlb_entry *result, void *user_data) +static bool test_virtual_write_tlb_fill(uc_engine *uc, uint64_t addr, + uc_mem_type type, uc_tlb_entry *result, + void *user_data) { if (addr < 0x1000) return false; @@ -572,11 +1930,12 @@ static void test_virtual_write(void) /* * mov rax, [0x2000] */ - char code[] = { 0x48, 0x8B, 0x04, 0x25, 0x00, 0x20, 0x00, 0x00 }; + char code[] = {0x48, 0x8B, 0x04, 0x25, 0x00, 0x20, 0x00, 0x00}; OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); OK(uc_ctl_tlb_mode(uc, UC_TLB_VIRTUAL)); - OK(uc_hook_add(uc, &hook, UC_HOOK_TLB_FILL, test_virtual_write_tlb_fill, NULL, 1, 0)); + OK(uc_hook_add(uc, &hook, UC_HOOK_TLB_FILL, test_virtual_write_tlb_fill, + NULL, 1, 0)); OK(uc_mem_map(uc, 0x0, 0x2000, UC_PROT_ALL)); OK(uc_vmem_write(uc, 0x1000, UC_PROT_EXEC, code, sizeof(code))); @@ -602,21 +1961,21 @@ static void test_mem_addr_size_wraparound(void) uint8_t buf[0x1000]; OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); - OK(uc_mem_map(uc, 0, 0x1000, UC_PROT_ALL)); + OK(uc_mem_map(uc, 0, 0x1000, UC_PROT_ALL)); OK(uc_mem_map(uc, 0xFFFFFFFFFFFFF000ULL, 0x1000, UC_PROT_ALL)); uc_assert_err(UC_ERR_READ_UNMAPPED, - uc_mem_read(uc, 0xFFFFFFFFFFFFF000ULL, buf, 0x2000)); + uc_mem_read(uc, 0xFFFFFFFFFFFFF000ULL, buf, 0x2000)); uc_assert_err(UC_ERR_WRITE_UNMAPPED, - uc_mem_write(uc, 0xFFFFFFFFFFFFF000ULL, buf, 0x2000)); + uc_mem_write(uc, 0xFFFFFFFFFFFFF000ULL, buf, 0x2000)); uc_assert_err(UC_ERR_NOMEM, - uc_mem_unmap(uc, 0xFFFFFFFFFFFFF000ULL, 0x2000)); - uc_assert_err(UC_ERR_NOMEM, - uc_mem_protect(uc, 0xFFFFFFFFFFFFF000ULL, 0x2000, UC_PROT_READ)); + uc_mem_unmap(uc, 0xFFFFFFFFFFFFF000ULL, 0x2000)); + uc_assert_err(UC_ERR_NOMEM, uc_mem_protect(uc, 0xFFFFFFFFFFFFF000ULL, + 0x2000, UC_PROT_READ)); // The non-wrapping single-page case must still work for both regions. OK(uc_mem_read(uc, 0xFFFFFFFFFFFFF000ULL, buf, 0x1000)); - OK(uc_mem_read(uc, 0, buf, 0x1000)); + OK(uc_mem_read(uc, 0, buf, 0x1000)); OK(uc_close(uc)); } @@ -642,15 +2001,15 @@ static void test_smc(void) r_rax = 0x1234; r_rsp = 0x5000; OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); - OK(uc_mem_map (uc, 0x0, 0x1000, UC_PROT_ALL)); // text - OK(uc_mem_map (uc, 0x4000, 0x1000, UC_PROT_READ|UC_PROT_WRITE)); // stack - OK(uc_mem_write(uc, 0x0, code, sizeof(code)-1)); + OK(uc_mem_map(uc, 0x0, 0x1000, UC_PROT_ALL)); // text + OK(uc_mem_map(uc, 0x4000, 0x1000, UC_PROT_READ | UC_PROT_WRITE)); // stack + OK(uc_mem_write(uc, 0x0, code, sizeof(code) - 1)); OK(uc_reg_write(uc, UC_X86_REG_RAX, &r_rax)); OK(uc_reg_write(uc, UC_X86_REG_RSP, &r_rsp)); - OK(uc_emu_start(uc, 0x4, sizeof(code)-1, 0, 0)); + OK(uc_emu_start(uc, 0x4, sizeof(code) - 1, 0, 0)); OK(uc_reg_read(uc, UC_X86_REG_RAX, &r_rax)); - OK(uc_mem_read(uc, 0x0, code, sizeof(code)-1)); + OK(uc_mem_read(uc, 0x0, code, sizeof(code) - 1)); TEST_CHECK(r_rax == 0x1234); TEST_CHECK((code[2] & 0xFF) == 0xC8); @@ -669,11 +2028,11 @@ static void test_tlbdirty_exec(void) char code[] = ("\x40"); // inc eax eax = 41; OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); - OK(uc_reg_write (uc, UC_X86_REG_EAX, &eax)); - OK(uc_mem_map (uc, 0x0, 0x1000, UC_PROT_READ|UC_PROT_WRITE)); - OK(uc_mem_write (uc, 0x0, code, sizeof(code)-1)); - OK(uc_mem_protect(uc, 0x0, 0x1000, UC_PROT_READ|UC_PROT_EXEC)); - OK(uc_emu_start (uc, 0x0, sizeof(code)-1, 0, 0)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_mem_map(uc, 0x0, 0x1000, UC_PROT_READ | UC_PROT_WRITE)); + OK(uc_mem_write(uc, 0x0, code, sizeof(code) - 1)); + OK(uc_mem_protect(uc, 0x0, 0x1000, UC_PROT_READ | UC_PROT_EXEC)); + OK(uc_emu_start(uc, 0x0, sizeof(code) - 1, 0, 0)); OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); TEST_CHECK(eax == 42); @@ -681,26 +2040,708 @@ static void test_tlbdirty_exec(void) OK(uc_close(uc)); } -TEST_LIST = {{"test_map_correct", test_map_correct}, - {"test_map_wrapping", test_map_wrapping}, - {"test_mem_protect", test_mem_protect}, - {"test_splitting_mem_unmap", test_splitting_mem_unmap}, - {"test_splitting_mmio_unmap", test_splitting_mmio_unmap}, - {"test_mem_protect_map_ptr", test_mem_protect_map_ptr}, - {"test_map_at_the_end", test_map_at_the_end}, - {"test_map_wrap", test_map_wrap}, - {"test_map_big_memory", test_map_big_memory}, - {"test_mem_protect_remove_exec", test_mem_protect_remove_exec}, - {"test_mem_protect_mmio", test_mem_protect_mmio}, - {"test_snapshot", test_snapshot}, - {"test_snapshot_with_vtlb", test_snapshot_with_vtlb}, - {"test_context_snapshot", test_context_snapshot}, - {"test_snapshot_unmap", test_snapshot_unmap}, - {"test_mem_read_and_write_large_memory_block", - test_mem_read_and_write_large_memory_block}, - {"test_virtual_to_physical", test_virtual_to_physical}, - {"test_virtual_write", test_virtual_write}, - {"test_mem_addr_size_wraparound", test_mem_addr_size_wraparound}, - {"test_smc", test_smc}, - {"test_tlbdirty_exec", test_tlbdirty_exec}, - {NULL, NULL}}; +#define TEST_MEM_HOOK_CODE 0x1000 +#define TEST_MEM_HOOK_DATA 0x3000 +#define TEST_MEM_HOOK_EVENT_CAPACITY 16 + +typedef struct TestMemoryHookEvent { + uc_mem_type type; + uint64_t address; + uint64_t pc; + int size; + int64_t value; +} TestMemoryHookEvent; + +typedef struct TestMemoryHookData { + TestMemoryHookEvent events[TEST_MEM_HOOK_EVENT_CAPACITY]; + size_t event_count; + uint32_t read_value; + bool stop_on_invalid; + bool stopped; +} TestMemoryHookData; + +static void test_memory_hook_record(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, + TestMemoryHookData *data) +{ + TestMemoryHookEvent *event; + uint32_t eip; + + if (!TEST_CHECK(data->event_count < TEST_MEM_HOOK_EVENT_CAPACITY)) { + return; + } + + event = &data->events[data->event_count++]; + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + event->type = type; + event->address = address; + event->pc = eip; + event->size = size; + event->value = value; +} + +static void test_memory_hook_callback(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, + void *user_data) +{ + test_memory_hook_record(uc, type, address, size, value, + (TestMemoryHookData *)user_data); +} + +static bool test_memory_invalid_hook_callback(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + TestMemoryHookData *data = (TestMemoryHookData *)user_data; + const uint8_t fetch_code[] = {0x40}; + uint64_t page = address & ~0xfffULL; + + test_memory_hook_record(uc, type, address, size, value, data); + + switch (type) { + case UC_MEM_READ_UNMAPPED: + OK(uc_mem_map(uc, page, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address, &data->read_value, + sizeof(data->read_value))); + break; + case UC_MEM_WRITE_UNMAPPED: + OK(uc_mem_map(uc, page, 0x1000, UC_PROT_ALL)); + break; + case UC_MEM_FETCH_UNMAPPED: + OK(uc_mem_map(uc, page, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address, fetch_code, sizeof(fetch_code))); + break; + case UC_MEM_READ_PROT: + case UC_MEM_WRITE_PROT: + case UC_MEM_FETCH_PROT: + OK(uc_mem_protect(uc, page, 0x1000, UC_PROT_ALL)); + break; + default: + TEST_CHECK(false); + return false; + } + + if (data->stop_on_invalid && !data->stopped) { + data->stopped = true; + OK(uc_emu_stop(uc)); + } + + return true; +} + +static void test_memory_hook_assert_event(const TestMemoryHookData *data, + size_t index, uc_mem_type type, + uint64_t address, + uint64_t expected_pc, int size) +{ + TEST_CHECK(index < data->event_count); + if (index >= data->event_count) { + return; + } + + TEST_CHECK(data->events[index].type == type); + TEST_CHECK(data->events[index].address == address); + TEST_CHECK(data->events[index].pc == expected_pc); + TEST_CHECK(data->events[index].size == size); +} + +typedef enum TestInsnTraceType { + TEST_INSN_TRACE_FETCH, + TEST_INSN_TRACE_CODE, +} TestInsnTraceType; + +typedef struct TestInsnTraceEvent { + TestInsnTraceType type; + uint64_t address; + uint32_t pc; + uint32_t size; + uint32_t eax; + uint32_t ebx; +} TestInsnTraceEvent; + +typedef struct TestInsnTrace { + TestInsnTraceEvent events[16]; + size_t count; + bool stop_on_code; +} TestInsnTrace; + +static void test_insn_trace_record(uc_engine *uc, TestInsnTraceType type, + uint64_t address, uint32_t size, + TestInsnTrace *trace) +{ + TestInsnTraceEvent *event; + + if (!TEST_CHECK(trace->count < + sizeof(trace->events) / sizeof(trace->events[0]))) { + return; + } + + event = &trace->events[trace->count++]; + event->type = type; + event->address = address; + event->size = size; + OK(uc_reg_read(uc, UC_X86_REG_EIP, &event->pc)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &event->eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &event->ebx)); +} + +static void test_insn_fetch_callback(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, + void *user_data) +{ + TEST_CHECK(type == UC_MEM_FETCH); + TEST_CHECK(value == 0); + test_insn_trace_record(uc, TEST_INSN_TRACE_FETCH, address, size, + (TestInsnTrace *)user_data); +} + +static void test_insn_code_callback(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + TestInsnTrace *trace = (TestInsnTrace *)user_data; + + test_insn_trace_record(uc, TEST_INSN_TRACE_CODE, address, size, trace); + if (trace->stop_on_code) { + OK(uc_emu_stop(uc)); + } +} + +static void test_insn_trace_assert_event(const TestInsnTrace *trace, + size_t index, TestInsnTraceType type, + uint64_t address, uint32_t size, + uint32_t eax, uint32_t ebx) +{ + TEST_CHECK(index < trace->count); + if (index >= trace->count) { + return; + } + + TEST_CHECK(trace->events[index].type == type); + TEST_CHECK(trace->events[index].address == address); + TEST_CHECK(trace->events[index].pc == address); + TEST_CHECK(trace->events[index].size == size); + TEST_CHECK(trace->events[index].eax == eax); + TEST_CHECK(trace->events[index].ebx == ebx); +} + +static void test_mem_fetch_hook(void) +{ + const uint8_t code[] = { + 0x40, /* inc eax */ + 0x83, 0xc3, 0x02, /* add ebx,2 */ + }; + TestInsnTrace trace = {0}; + uint32_t eax = 0; + uint32_t ebx = 0; + uc_engine *uc; + uc_hook code_hook; + uc_hook fetch_hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, TEST_MEM_HOOK_CODE, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, TEST_MEM_HOOK_CODE, code, sizeof(code))); + + /* Populate the TB cache before installing either hook. */ + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 2)); + + eax = 0; + ebx = 0; + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_hook_add(uc, &fetch_hook, UC_HOOK_MEM_FETCH, test_insn_fetch_callback, + &trace, 1, 0)); + OK(uc_hook_add(uc, &code_hook, UC_HOOK_CODE, test_insn_code_callback, + &trace, 1, 0)); + + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 2)); + TEST_CHECK(trace.count == 4); + test_insn_trace_assert_event(&trace, 0, TEST_INSN_TRACE_FETCH, + TEST_MEM_HOOK_CODE, 1, 0, 0); + test_insn_trace_assert_event(&trace, 1, TEST_INSN_TRACE_CODE, + TEST_MEM_HOOK_CODE, 1, 0, 0); + test_insn_trace_assert_event(&trace, 2, TEST_INSN_TRACE_FETCH, + TEST_MEM_HOOK_CODE + 1, 3, 1, 0); + test_insn_trace_assert_event(&trace, 3, TEST_INSN_TRACE_CODE, + TEST_MEM_HOOK_CODE + 1, 3, 1, 0); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx)); + TEST_CHECK(eax == 1); + TEST_CHECK(ebx == 2); + + eax = 0; + ebx = 0; + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 2)); + TEST_CHECK(trace.count == 8); + test_insn_trace_assert_event(&trace, 4, TEST_INSN_TRACE_FETCH, + TEST_MEM_HOOK_CODE, 1, 0, 0); + test_insn_trace_assert_event(&trace, 5, TEST_INSN_TRACE_CODE, + TEST_MEM_HOOK_CODE, 1, 0, 0); + test_insn_trace_assert_event(&trace, 6, TEST_INSN_TRACE_FETCH, + TEST_MEM_HOOK_CODE + 1, 3, 1, 0); + test_insn_trace_assert_event(&trace, 7, TEST_INSN_TRACE_CODE, + TEST_MEM_HOOK_CODE + 1, 3, 1, 0); + + OK(uc_hook_del(uc, fetch_hook)); + + eax = 0; + ebx = 0; + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 2)); + TEST_CHECK(trace.count == 10); + test_insn_trace_assert_event(&trace, 8, TEST_INSN_TRACE_CODE, + TEST_MEM_HOOK_CODE, 1, 0, 0); + test_insn_trace_assert_event(&trace, 9, TEST_INSN_TRACE_CODE, + TEST_MEM_HOOK_CODE + 1, 3, 1, 0); + + OK(uc_hook_del(uc, code_hook)); + OK(uc_close(uc)); +} + +static void test_mem_fetch_precedes_code_stop(void) +{ + const uint8_t code[] = {0x40}; + TestInsnTrace trace = { + .stop_on_code = true, + }; + uint32_t eax = 0; + uc_engine *uc; + uc_hook code_hook; + uc_hook fetch_hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, TEST_MEM_HOOK_CODE, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, TEST_MEM_HOOK_CODE, code, sizeof(code))); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_hook_add(uc, &code_hook, UC_HOOK_CODE, test_insn_code_callback, + &trace, 1, 0)); + OK(uc_hook_add(uc, &fetch_hook, UC_HOOK_MEM_FETCH, test_insn_fetch_callback, + &trace, 1, 0)); + + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 1)); + TEST_CHECK(trace.count == 2); + test_insn_trace_assert_event(&trace, 0, TEST_INSN_TRACE_FETCH, + TEST_MEM_HOOK_CODE, 1, 0, 0); + test_insn_trace_assert_event(&trace, 1, TEST_INSN_TRACE_CODE, + TEST_MEM_HOOK_CODE, 1, 0, 0); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 0); + + OK(uc_hook_del(uc, fetch_hook)); + OK(uc_hook_del(uc, code_hook)); + OK(uc_close(uc)); +} + +static void test_mem_fetch_range_and_user_data(void) +{ + const uint8_t code[] = {0x40, 0x43}; + TestMemoryHookData initial = {0}; + TestMemoryHookData replacement = {0}; + uint32_t eax = 0; + uint32_t ebx = 0; + uc_engine *uc; + uc_hook hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, TEST_MEM_HOOK_CODE, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, TEST_MEM_HOOK_CODE, code, sizeof(code))); + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 2)); + + eax = 0; + ebx = 0; + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_FETCH, test_memory_hook_callback, + &initial, TEST_MEM_HOOK_CODE + 1, TEST_MEM_HOOK_CODE + 1)); + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 2)); + TEST_CHECK(initial.event_count == 1); + test_memory_hook_assert_event(&initial, 0, UC_MEM_FETCH, + TEST_MEM_HOOK_CODE + 1, + TEST_MEM_HOOK_CODE + 1, 1); + + OK(uc_hook_set_user_data(uc, hook, &replacement)); + eax = 0; + ebx = 0; + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 2)); + TEST_CHECK(initial.event_count == 1); + TEST_CHECK(replacement.event_count == 1); + test_memory_hook_assert_event(&replacement, 0, UC_MEM_FETCH, + TEST_MEM_HOOK_CODE + 1, + TEST_MEM_HOOK_CODE + 1, 1); + + OK(uc_hook_del(uc, hook)); + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 2)); + TEST_CHECK(replacement.event_count == 1); + + OK(uc_close(uc)); +} + +typedef struct TestSelfDeletingFetch { + uc_hook hook; + size_t count; +} TestSelfDeletingFetch; + +static void test_mem_fetch_self_delete_callback(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + TestSelfDeletingFetch *data = (TestSelfDeletingFetch *)user_data; + + TEST_CHECK(type == UC_MEM_FETCH); + data->count++; + if (data->count == 1) { + OK(uc_hook_del(uc, data->hook)); + } +} + +static void test_mem_fetch_self_delete(void) +{ + const uint8_t code[] = {0x40, 0x43}; + TestSelfDeletingFetch data = {0}; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, TEST_MEM_HOOK_CODE, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, TEST_MEM_HOOK_CODE, code, sizeof(code))); + OK(uc_hook_add(uc, &data.hook, UC_HOOK_MEM_FETCH, + test_mem_fetch_self_delete_callback, &data, 1, 0)); + + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 2)); + TEST_CHECK(data.count == 1); + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 2)); + TEST_CHECK(data.count == 1); + + OK(uc_close(uc)); +} + +static void test_mem_read_prot_stop_resume(void) +{ + const uint8_t code[] = {0xa1, 0x00, 0x30, 0x00, 0x00}; + TestMemoryHookData data = { + .read_value = LEINT32(0x78563412), + .stop_on_invalid = true, + }; + uint32_t eax = 0xaaaaaaaa; + uint32_t eip; + uc_engine *uc; + uc_hook read_hook; + uc_hook prot_hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, TEST_MEM_HOOK_CODE, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, TEST_MEM_HOOK_CODE, code, sizeof(code))); + OK(uc_mem_map(uc, TEST_MEM_HOOK_DATA, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, TEST_MEM_HOOK_DATA, &data.read_value, + sizeof(data.read_value))); + OK(uc_mem_protect(uc, TEST_MEM_HOOK_DATA, 0x1000, UC_PROT_WRITE)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_hook_add(uc, &read_hook, UC_HOOK_MEM_READ, test_memory_hook_callback, + &data, 1, 0)); + OK(uc_hook_add(uc, &prot_hook, UC_HOOK_MEM_READ_PROT, + test_memory_invalid_hook_callback, &data, 1, 0)); + + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 1)); + TEST_CHECK(data.event_count == 2); + test_memory_hook_assert_event(&data, 0, UC_MEM_READ, TEST_MEM_HOOK_DATA, + TEST_MEM_HOOK_CODE, 4); + test_memory_hook_assert_event(&data, 1, UC_MEM_READ_PROT, + TEST_MEM_HOOK_DATA, TEST_MEM_HOOK_CODE, 4); + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + TEST_CHECK(eip == TEST_MEM_HOOK_CODE); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 0xaaaaaaaa); + + OK(uc_emu_start(uc, eip, TEST_MEM_HOOK_CODE + sizeof(code), 0, 1)); + TEST_CHECK(data.event_count == 3); + test_memory_hook_assert_event(&data, 2, UC_MEM_READ, TEST_MEM_HOOK_DATA, + TEST_MEM_HOOK_CODE, 4); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 0x78563412); + + OK(uc_hook_del(uc, prot_hook)); + OK(uc_hook_del(uc, read_hook)); + OK(uc_mem_protect(uc, TEST_MEM_HOOK_DATA, 0x1000, UC_PROT_WRITE)); + uc_assert_err(UC_ERR_READ_PROT, + uc_emu_start(uc, TEST_MEM_HOOK_CODE, + TEST_MEM_HOOK_CODE + sizeof(code), 0, 1)); + TEST_CHECK(data.event_count == 3); + + OK(uc_close(uc)); +} + +static void test_mem_write_prot_recovery(void) +{ + const uint8_t code[] = { + 0xc7, 0x05, 0x00, 0x30, 0x00, 0x00, 0x78, 0x56, 0x34, 0x12, + }; + TestMemoryHookData data = {0}; + uint32_t memory = 0; + uc_engine *uc; + uc_hook write_hook; + uc_hook prot_hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, TEST_MEM_HOOK_CODE, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, TEST_MEM_HOOK_CODE, code, sizeof(code))); + OK(uc_mem_map(uc, TEST_MEM_HOOK_DATA, 0x1000, UC_PROT_READ)); + OK(uc_hook_add(uc, &write_hook, UC_HOOK_MEM_WRITE, + test_memory_hook_callback, &data, 1, 0)); + OK(uc_hook_add(uc, &prot_hook, UC_HOOK_MEM_WRITE_PROT, + test_memory_invalid_hook_callback, &data, 1, 0)); + + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 1)); + TEST_CHECK(data.event_count == 2); + test_memory_hook_assert_event(&data, 0, UC_MEM_WRITE, TEST_MEM_HOOK_DATA, + TEST_MEM_HOOK_CODE, 4); + test_memory_hook_assert_event(&data, 1, UC_MEM_WRITE_PROT, + TEST_MEM_HOOK_DATA, TEST_MEM_HOOK_CODE, 4); + TEST_CHECK(data.events[0].value == 0x12345678); + TEST_CHECK(data.events[1].value == 0x12345678); + OK(uc_mem_read(uc, TEST_MEM_HOOK_DATA, &memory, sizeof(memory))); + TEST_CHECK(LEINT32(memory) == 0x12345678); + + OK(uc_hook_del(uc, prot_hook)); + OK(uc_hook_del(uc, write_hook)); + OK(uc_mem_protect(uc, TEST_MEM_HOOK_DATA, 0x1000, UC_PROT_READ)); + uc_assert_err(UC_ERR_WRITE_PROT, + uc_emu_start(uc, TEST_MEM_HOOK_CODE, + TEST_MEM_HOOK_CODE + sizeof(code), 0, 1)); + TEST_CHECK(data.event_count == 2); + OK(uc_close(uc)); +} + +static void test_mem_fetch_prot_recovery(void) +{ + const uint8_t code[] = {0x40}; + TestMemoryHookData data = {0}; + uint32_t eax = 0; + uc_engine *uc; + uc_hook fetch_hook; + uc_hook prot_hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, TEST_MEM_HOOK_CODE, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, TEST_MEM_HOOK_CODE, code, sizeof(code))); + OK(uc_mem_protect(uc, TEST_MEM_HOOK_CODE, 0x1000, + UC_PROT_READ | UC_PROT_WRITE)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_hook_add(uc, &fetch_hook, UC_HOOK_MEM_FETCH, + test_memory_hook_callback, &data, 1, 0)); + OK(uc_hook_add(uc, &prot_hook, UC_HOOK_MEM_FETCH_PROT, + test_memory_invalid_hook_callback, &data, 1, 0)); + + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 1)); + TEST_CHECK(data.event_count == 2); + test_memory_hook_assert_event(&data, 0, UC_MEM_FETCH_PROT, + TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE, 1); + test_memory_hook_assert_event(&data, 1, UC_MEM_FETCH, TEST_MEM_HOOK_CODE, + TEST_MEM_HOOK_CODE, 1); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 1); + + OK(uc_hook_del(uc, prot_hook)); + OK(uc_hook_del(uc, fetch_hook)); + OK(uc_mem_protect(uc, TEST_MEM_HOOK_CODE, 0x1000, + UC_PROT_READ | UC_PROT_WRITE)); + uc_assert_err(UC_ERR_FETCH_PROT, + uc_emu_start(uc, TEST_MEM_HOOK_CODE, + TEST_MEM_HOOK_CODE + sizeof(code), 0, 1)); + TEST_CHECK(data.event_count == 2); + + OK(uc_close(uc)); +} + +static void test_mem_read_unmapped_recovery(void) +{ + const uint8_t code[] = {0xa1, 0x00, 0x30, 0x00, 0x00}; + TestMemoryHookData data = {.read_value = LEINT32(0x78563412)}; + uint32_t eax = 0; + uc_engine *uc; + uc_hook hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, TEST_MEM_HOOK_CODE, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, TEST_MEM_HOOK_CODE, code, sizeof(code))); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ_UNMAPPED, + test_memory_invalid_hook_callback, &data, 1, 0)); + + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 1)); + TEST_CHECK(data.event_count == 1); + test_memory_hook_assert_event(&data, 0, UC_MEM_READ_UNMAPPED, + TEST_MEM_HOOK_DATA, TEST_MEM_HOOK_CODE, 4); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 0x78563412); + + OK(uc_hook_del(uc, hook)); + OK(uc_mem_unmap(uc, TEST_MEM_HOOK_DATA, 0x1000)); + uc_assert_err(UC_ERR_READ_UNMAPPED, + uc_emu_start(uc, TEST_MEM_HOOK_CODE, + TEST_MEM_HOOK_CODE + sizeof(code), 0, 1)); + TEST_CHECK(data.event_count == 1); + OK(uc_close(uc)); +} + +static void test_mem_write_unmapped_recovery(void) +{ + const uint8_t code[] = { + 0xc7, 0x05, 0x00, 0x30, 0x00, 0x00, 0x78, 0x56, 0x34, 0x12, + }; + TestMemoryHookData data = {0}; + uint32_t memory = 0; + uc_engine *uc; + uc_hook hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, TEST_MEM_HOOK_CODE, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, TEST_MEM_HOOK_CODE, code, sizeof(code))); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_WRITE_UNMAPPED, + test_memory_invalid_hook_callback, &data, 1, 0)); + + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + sizeof(code), + 0, 1)); + TEST_CHECK(data.event_count == 1); + test_memory_hook_assert_event(&data, 0, UC_MEM_WRITE_UNMAPPED, + TEST_MEM_HOOK_DATA, TEST_MEM_HOOK_CODE, 4); + TEST_CHECK(data.events[0].value == 0x12345678); + OK(uc_mem_read(uc, TEST_MEM_HOOK_DATA, &memory, sizeof(memory))); + TEST_CHECK(LEINT32(memory) == 0x12345678); + + OK(uc_hook_del(uc, hook)); + OK(uc_mem_unmap(uc, TEST_MEM_HOOK_DATA, 0x1000)); + uc_assert_err(UC_ERR_WRITE_UNMAPPED, + uc_emu_start(uc, TEST_MEM_HOOK_CODE, + TEST_MEM_HOOK_CODE + sizeof(code), 0, 1)); + TEST_CHECK(data.event_count == 1); + OK(uc_close(uc)); +} + +static void test_mem_fetch_unmapped_recovery(void) +{ + TestMemoryHookData data = {0}; + uint32_t eax = 0; + uc_engine *uc; + uc_hook hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_FETCH_UNMAPPED, + test_memory_invalid_hook_callback, &data, 1, 0)); + + OK(uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + 1, 0, 1)); + TEST_CHECK(data.event_count == 1); + test_memory_hook_assert_event(&data, 0, UC_MEM_FETCH_UNMAPPED, + TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE, 1); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 1); + + OK(uc_hook_del(uc, hook)); + OK(uc_mem_unmap(uc, TEST_MEM_HOOK_CODE, 0x1000)); + uc_assert_err( + UC_ERR_FETCH_UNMAPPED, + uc_emu_start(uc, TEST_MEM_HOOK_CODE, TEST_MEM_HOOK_CODE + 1, 0, 1)); + TEST_CHECK(data.event_count == 1); + OK(uc_close(uc)); +} + +TEST_LIST = { + {"test_map_correct", test_map_correct}, + {"test_map_wrapping", test_map_wrapping}, + {"test_mem_protect", test_mem_protect}, + {"test_splitting_mem_unmap", test_splitting_mem_unmap}, + {"test_splitting_mmio_unmap", test_splitting_mmio_unmap}, + {"test_mem_protect_map_ptr", test_mem_protect_map_ptr}, + {"test_mem_cross_region_access", test_mem_cross_region_access}, + {"test_mem_regions_topology", test_mem_regions_topology}, + {"test_map_at_the_end", test_map_at_the_end}, + {"test_terminal_two_page_unmap_first", + test_terminal_two_page_unmap_first}, + {"test_terminal_two_page_unmap_last", test_terminal_two_page_unmap_last}, + {"test_terminal_two_page_protect_first", + test_terminal_two_page_protect_first}, + {"test_terminal_two_page_protect_last", + test_terminal_two_page_protect_last}, + {"test_terminal_page_active_unmap", test_terminal_page_active_unmap}, + {"test_map_wrap", test_map_wrap}, + {"test_map_big_memory", test_map_big_memory}, + {"test_mem_protect_remove_exec", test_mem_protect_remove_exec}, + {"test_active_code_page_change", test_active_code_page_change}, + {"test_inactive_code_page_change", test_inactive_code_page_change}, + {"test_mmio_exec_write_unrelated_ram", test_mmio_exec_write_unrelated_ram}, + {"test_mem_protect_mmio", test_mem_protect_mmio}, + {"test_snapshot", test_snapshot}, + {"test_snapshot_code_restore_from_callback", + test_snapshot_code_restore_from_callback}, + {"test_snapshot_cow_disjoint_nested_patch", + test_snapshot_cow_disjoint_nested_patch}, + {"test_snapshot_nested_restore_outer_instruction", + test_snapshot_nested_restore_outer_instruction}, + {"test_snapshot_with_vtlb", test_snapshot_with_vtlb}, + {"test_context_snapshot", test_context_snapshot}, + {"test_context_cpu_resave_preserves_memory_snapshot", + test_context_cpu_resave_preserves_memory_snapshot}, + {"test_snapshot_unmap", test_snapshot_unmap}, + {"test_terminal_page_snapshot_restore", + test_terminal_page_snapshot_restore}, + {"test_snapshot_restore_skips_later_unmapped_regions", + test_snapshot_restore_skips_later_unmapped_regions}, + {"test_snapshot_empty_flatview", test_snapshot_empty_flatview}, + {"test_snapshot_replace_with_ram", test_snapshot_replace_with_ram}, + {"test_snapshot_cow_coordinates_and_remap", + test_snapshot_cow_coordinates_and_remap}, + {"test_snapshot_cow_replacement_restore", + test_snapshot_cow_replacement_restore}, + {"test_snapshot_forward_restore_same_level_aba", + test_snapshot_forward_restore_same_level_aba}, + {"test_snapshot_equal_priority_cow_leaves", + test_snapshot_equal_priority_cow_leaves}, + {"test_snapshot_repeated_root_leaf_forward_restore", + test_snapshot_repeated_root_leaf_forward_restore}, + {"test_snapshot_context_resave_free_ownership", + test_snapshot_context_resave_free_ownership}, + {"test_snapshot_final_release_restores_memory_apis", + test_snapshot_final_release_restores_memory_apis}, + {"test_snapshot_close_with_retained_mappings", + test_snapshot_close_with_retained_mappings}, + {"test_snapshot_replace_with_host_memory", + test_snapshot_replace_with_host_memory}, + {"test_snapshot_existing_mmio_write", test_snapshot_existing_mmio_write}, + {"test_snapshot_replace_with_mmio", test_snapshot_replace_with_mmio}, + {"test_mem_read_and_write_large_memory_block", + test_mem_read_and_write_large_memory_block}, + {"test_virtual_to_physical", test_virtual_to_physical}, + {"test_virtual_write", test_virtual_write}, + {"test_mem_addr_size_wraparound", test_mem_addr_size_wraparound}, + {"test_smc", test_smc}, + {"test_tlbdirty_exec", test_tlbdirty_exec}, + {"test_mem_fetch_hook", test_mem_fetch_hook}, + {"test_mem_fetch_precedes_code_stop", test_mem_fetch_precedes_code_stop}, + {"test_mem_fetch_range_and_user_data", test_mem_fetch_range_and_user_data}, + {"test_mem_fetch_self_delete", test_mem_fetch_self_delete}, + {"test_mem_read_prot_stop_resume", test_mem_read_prot_stop_resume}, + {"test_mem_write_prot_recovery", test_mem_write_prot_recovery}, + {"test_mem_fetch_prot_recovery", test_mem_fetch_prot_recovery}, + {"test_mem_read_unmapped_recovery", test_mem_read_unmapped_recovery}, + {"test_mem_write_unmapped_recovery", test_mem_write_unmapped_recovery}, + {"test_mem_fetch_unmapped_recovery", test_mem_fetch_unmapped_recovery}, + {NULL, NULL}}; diff --git a/tests/unit/test_mips.c b/tests/unit/test_mips.c index 0192a593be..60c1cb3bf5 100644 --- a/tests/unit/test_mips.c +++ b/tests/unit/test_mips.c @@ -12,6 +12,15 @@ const uint64_t code_len = 0x4000; #define MIPS_CP0_STATUS_FR (1u << 26) #define MIPS_CP0_STATUS_CU1 (1u << 29) +#define MIPS_CP0_STATUS_MX (1u << 24) + +#define MIPS_MSACSR_RM_UP 2u +#define MIPS_MSACSR_FLAG_INEXACT (1u << 2) +#define MIPS_MSACSR_CAUSE_INEXACT (1u << 12) +#define MIPS_MSACSR_FS (1u << 24) + +#define MIPS_FCSR_RM_DOWN 3u +#define MIPS_FCSR_FS (1u << 24) static uint32_t mips_bitswap32(uint32_t value) { @@ -66,6 +75,15 @@ static void enable_mips64_fpu(uc_engine *uc) OK(uc_reg_write(uc, UC_MIPS_REG_CP0_STATUS, &status)); } +static void enable_mips32_dsp(uc_engine *uc) +{ + uint32_t status; + + OK(uc_reg_read(uc, UC_MIPS_REG_CP0_STATUS, &status)); + status |= MIPS_CP0_STATUS_MX; + OK(uc_reg_write(uc, UC_MIPS_REG_CP0_STATUS, &status)); +} + static void test_mips_el_ori(void) { uc_engine *uc; @@ -152,6 +170,332 @@ static void test_mips_stop_at_delay_slot(void) OK(uc_close(uc)); } +typedef struct MipsDelaySlotStop { + uc_err error; + uint32_t calls; +} MipsDelaySlotStop; + +static void mips_delay_slot_stop_cb(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + MipsDelaySlotStop *stop = (MipsDelaySlotStop *)user_data; + + stop->calls++; + stop->error = uc_emu_stop(uc); +} + +static void test_mips_delay_slot_pending_stop(void) +{ + const uint8_t code[] = { + 0x01, 0x00, 0x00, 0x10, /* beq $zero,$zero,code_start + 8 */ + 0x01, 0x00, 0x42, 0x24, /* addiu $v0,$v0,1 */ + 0x01, 0x00, 0x63, 0x24, /* addiu $v1,$v1,1 */ + }; + MipsDelaySlotStop stop = {0}; + uint32_t pc; + uint32_t v0 = 0; + uint32_t v1 = 0; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_MIPS, + UC_MODE_MIPS32 | UC_MODE_LITTLE_ENDIAN, + (const char *)code, sizeof(code)); + OK(uc_reg_write(uc, UC_MIPS_REG_V0, &v0)); + OK(uc_reg_write(uc, UC_MIPS_REG_V1, &v1)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, mips_delay_slot_stop_cb, + &stop, code_start + 4, code_start + 4)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(stop.error); + OK(uc_reg_read(uc, UC_MIPS_REG_PC, &pc)); + OK(uc_reg_read(uc, UC_MIPS_REG_V0, &v0)); + OK(uc_reg_read(uc, UC_MIPS_REG_V1, &v1)); + TEST_CHECK(stop.calls == 1); + TEST_CHECK_(pc == code_start + 8, "pc=0x%x", pc); + TEST_CHECK(v0 == 1); + TEST_CHECK(v1 == 0); + + OK(uc_close(uc)); +} + +typedef struct MipsCodeTrace { + uint64_t address[3]; + uint32_t size[3]; + uint32_t count; +} MipsCodeTrace; + +static void mips_code_trace_hook(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + MipsCodeTrace *trace = (MipsCodeTrace *)user_data; + + (void)uc; + if (trace->count < 3) { + trace->address[trace->count] = address; + trace->size[trace->count] = size; + } + trace->count++; +} + +static void mips_fetch_trace_hook(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, + void *user_data) +{ + TEST_CHECK(type == UC_MEM_FETCH); + TEST_CHECK(value == 0); + mips_code_trace_hook(uc, address, size, user_data); +} + +static void test_mips_not_taken_branch_likely_code_hook(void) +{ + const uint32_t code[] = { + BEINT32(0x55090001), /* bnel t0, t1, +1 */ + BEINT32(0x24420001), /* addiu v0, v0, 1 */ + BEINT32(0x24630001), /* addiu v1, v1, 1 */ + }; + MipsCodeTrace code_trace = { 0 }; + MipsCodeTrace fetch_trace = { 0 }; + uc_engine *uc; + uc_hook code_hook; + uc_hook fetch_hook; + uint32_t equal = 0x12345678; + uint32_t unequal = 0; + uint32_t v0 = 0; + uint32_t v1 = 0; + + uc_common_setup(&uc, UC_ARCH_MIPS, + UC_MODE_MIPS32 | UC_MODE_BIG_ENDIAN, + (const char *)code, sizeof(code)); + OK(uc_reg_write(uc, UC_MIPS_REG_T0, &equal)); + OK(uc_reg_write(uc, UC_MIPS_REG_T1, &equal)); + OK(uc_hook_add(uc, &code_hook, UC_HOOK_CODE, mips_code_trace_hook, + &code_trace, 1, 0)); + OK(uc_hook_add(uc, &fetch_hook, UC_HOOK_MEM_FETCH, + mips_fetch_trace_hook, &fetch_trace, 1, 0)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_MIPS_REG_V0, &v0)); + OK(uc_reg_read(uc, UC_MIPS_REG_V1, &v1)); + TEST_CHECK(v0 == 0); + TEST_CHECK(v1 == 1); + TEST_CHECK_(code_trace.count == 2, "code_trace.count=%u", + code_trace.count); + TEST_CHECK(code_trace.address[0] == code_start); + TEST_CHECK(code_trace.address[1] == code_start + 8); + TEST_CHECK(code_trace.size[0] == 4); + TEST_CHECK(code_trace.size[1] == 4); + TEST_CHECK_(fetch_trace.count == 2, "fetch_trace.count=%u", + fetch_trace.count); + TEST_CHECK(fetch_trace.address[0] == code_start); + TEST_CHECK(fetch_trace.address[1] == code_start + 8); + TEST_CHECK(fetch_trace.size[0] == 4); + TEST_CHECK(fetch_trace.size[1] == 4); + + memset(&code_trace, 0, sizeof(code_trace)); + memset(&fetch_trace, 0, sizeof(fetch_trace)); + v0 = 0; + v1 = 0; + OK(uc_reg_write(uc, UC_MIPS_REG_T1, &unequal)); + OK(uc_reg_write(uc, UC_MIPS_REG_V0, &v0)); + OK(uc_reg_write(uc, UC_MIPS_REG_V1, &v1)); + + /* Reuse the cached TB and take the branch-likely delay slot. */ + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_MIPS_REG_V0, &v0)); + OK(uc_reg_read(uc, UC_MIPS_REG_V1, &v1)); + TEST_CHECK(v0 == 1); + TEST_CHECK(v1 == 1); + TEST_CHECK(code_trace.count == 3); + TEST_CHECK(fetch_trace.count == 3); + for (uint32_t i = 0; i < 3; i++) { + TEST_CHECK(code_trace.address[i] == code_start + i * 4); + TEST_CHECK(fetch_trace.address[i] == code_start + i * 4); + TEST_CHECK(code_trace.size[i] == 4); + TEST_CHECK(fetch_trace.size[i] == 4); + } + + OK(uc_hook_del(uc, fetch_hook)); + OK(uc_hook_del(uc, code_hook)); + OK(uc_close(uc)); +} + +typedef struct MipsDelaySlotCallbackSmc { + uint64_t patch_address; + uint64_t address[3]; + uint32_t pc[3]; + uint32_t size[3]; + uint32_t replacement; + uint32_t count; + uc_err read_error; + uc_err write_error; + bool patched; +} MipsDelaySlotCallbackSmc; + +static void mips_delay_slot_callback_smc_hook(uc_engine *uc, + uint64_t address, + uint32_t size, void *user_data) +{ + MipsDelaySlotCallbackSmc *smc = + (MipsDelaySlotCallbackSmc *)user_data; + uint32_t index = smc->count; + uint32_t pc = 0; + uc_err err; + + err = uc_reg_read(uc, UC_MIPS_REG_PC, &pc); + if (smc->read_error == UC_ERR_OK) { + smc->read_error = err; + } + if (index < 3) { + smc->address[index] = address; + smc->pc[index] = pc; + smc->size[index] = size; + } + smc->count++; + + if (smc->count == 2) { + smc->patched = true; + smc->write_error = + uc_mem_write(uc, smc->patch_address, &smc->replacement, + sizeof(smc->replacement)); + } +} + +static void test_mips_big_endian_delay_slot_callback_smc(void) +{ + const uint32_t code[] = { + BEINT32(0x10000001), /* beq zero,zero,+1 */ + BEINT32(0x24020001), /* addiu v0,zero,1 */ + BEINT32(0x24030003), /* addiu v1,zero,3 */ + }; + MipsDelaySlotCallbackSmc smc = { + .patch_address = code_start + 4, + .replacement = BEINT32(0x24020002), /* addiu v0,zero,2 */ + .read_error = UC_ERR_OK, + .write_error = UC_ERR_OK, + }; + uc_engine *uc; + uc_hook hook; + uint32_t stored = 0; + uint32_t v0 = 0; + uint32_t v1 = 0; + uint32_t i; + + uc_common_setup(&uc, UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_BIG_ENDIAN, + (const char *)code, sizeof(code)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, + mips_delay_slot_callback_smc_hook, &smc, code_start, + code_start)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_MIPS_REG_V0, &v0)); + OK(uc_reg_read(uc, UC_MIPS_REG_V1, &v1)); + OK(smc.read_error); + TEST_CHECK(!smc.patched); + TEST_CHECK_(smc.count == 1, "first count=%u", smc.count); + TEST_CHECK_(smc.address[0] == code_start, "first address=0x%llx", + (unsigned long long)smc.address[0]); + TEST_CHECK_(smc.pc[0] == code_start, "first pc=0x%x", smc.pc[0]); + TEST_CHECK_(smc.size[0] == 4, "first size=%u", smc.size[0]); + TEST_CHECK_(v0 == 1, "first v0=0x%x", v0); + TEST_CHECK_(v1 == 3, "first v1=0x%x", v1); + + v0 = 0; + v1 = 0; + OK(uc_reg_write(uc, UC_MIPS_REG_V0, &v0)); + OK(uc_reg_write(uc, UC_MIPS_REG_V1, &v1)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(smc.read_error); + OK(smc.write_error); + OK(uc_mem_read(uc, smc.patch_address, &stored, sizeof(stored))); + OK(uc_reg_read(uc, UC_MIPS_REG_V0, &v0)); + OK(uc_reg_read(uc, UC_MIPS_REG_V1, &v1)); + TEST_CHECK(smc.patched); + TEST_CHECK_(smc.count == 3, "final count=%u", smc.count); + for (i = 0; i < 3; i++) { + TEST_CHECK_(smc.address[i] == code_start, + "address[%u]=0x%llx", i, + (unsigned long long)smc.address[i]); + TEST_CHECK_(smc.pc[i] == code_start, "pc[%u]=0x%x", i, smc.pc[i]); + TEST_CHECK_(smc.size[i] == 4, "size[%u]=%u", i, smc.size[i]); + } + TEST_CHECK_(stored == smc.replacement, "stored=0x%08x", stored); + TEST_CHECK_(v0 == 2, "second v0=0x%x", v0); + TEST_CHECK_(v1 == 3, "second v1=0x%x", v1); + + OK(uc_close(uc)); +} + +typedef struct MipsDelaySlotMemoryTrace { + uint64_t address; + uint64_t pc; + int size; + int64_t value; + uint32_t count; + uc_err read_error; +} MipsDelaySlotMemoryTrace; + +static void mips_delay_slot_memory_hook(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + MipsDelaySlotMemoryTrace *trace = (MipsDelaySlotMemoryTrace *)user_data; + uint32_t pc = 0; + + (void)type; + trace->read_error = uc_reg_read(uc, UC_MIPS_REG_PC, &pc); + trace->address = address; + trace->pc = pc; + trace->size = size; + trace->value = value; + trace->count++; +} + +static void test_mips_big_endian_delay_slot_memory_hook_pc(void) +{ + const uint32_t code[] = { + BEINT32(0x10000001), /* beq zero,zero,+1 */ + BEINT32(0xad090000), /* sw t1,0(t0) */ + BEINT32(0x24020007), /* addiu v0,zero,7 */ + }; + const uint64_t data_address = code_start + 0x2000; + MipsDelaySlotMemoryTrace trace = {.read_error = UC_ERR_OK}; + uc_engine *uc; + uc_hook hook; + uint32_t t0 = (uint32_t)data_address; + uint32_t t1 = 0x11223344; + uint32_t stored = 0; + uint32_t v0 = 0; + + uc_common_setup(&uc, UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_BIG_ENDIAN, + (const char *)code, sizeof(code)); + OK(uc_reg_write(uc, UC_MIPS_REG_T0, &t0)); + OK(uc_reg_write(uc, UC_MIPS_REG_T1, &t1)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_WRITE, mips_delay_slot_memory_hook, + &trace, data_address, data_address + sizeof(stored) - 1)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(trace.read_error); + OK(uc_mem_read(uc, data_address, &stored, sizeof(stored))); + OK(uc_reg_read(uc, UC_MIPS_REG_V0, &v0)); + TEST_CHECK_(trace.count == 1, "trace.count=%u", trace.count); + TEST_CHECK(trace.address == data_address); + TEST_CHECK_(trace.pc == code_start + 4, "trace.pc=0x%llx", + (unsigned long long)trace.pc); + TEST_CHECK(trace.size == 4); + TEST_CHECK((uint32_t)trace.value == t1); + TEST_CHECK(stored == BEINT32(t1)); + TEST_CHECK(v0 == 7); + + OK(uc_close(uc)); +} + static void test_mips_stop_delay_slot_from_qiling(void) { uc_engine *uc; @@ -347,6 +691,520 @@ static void test_mips_msa_w_reg_roundtrip(void) OK(uc_close(uc)); } +static void test_mips_msa_addv_b(void) +{ + const uint8_t addv_code[] = { + 0xce, 0x08, 0x02, 0x78, /* addv.b w3, w1, w2 */ + }; + const uint8_t enable_code[] = { + 0x05, 0x80, 0x88, 0x40, /* mtc0 t0, Config5, 5 */ + 0xce, 0x08, 0x02, 0x78, /* addv.b w3, w1, w2 */ + }; + const uint8_t disable_code[] = { + 0x05, 0x80, 0x88, 0x40, /* mtc0 t0, Config5, 5 */ + 0xce, 0x08, 0x02, 0x78, /* addv.b w3, w1, w2 */ + }; + const uint8_t lhs[16] = { + 0x00, 0x01, 0x7f, 0x80, 0xfe, 0xff, 0x10, 0x20, + 0x30, 0x40, 0x50, 0x60, 0x70, 0x90, 0xa0, 0xf0, + }; + const uint8_t rhs[16] = { + 0x01, 0xff, 0x01, 0x80, 0x02, 0x01, 0xf0, 0xe0, + 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x70, 0x60, 0x20, + }; + uint8_t expected[16]; + uint8_t result[16] = { 0 }; + uint32_t status; + uint32_t t0; + uc_engine *uc; + size_t i; + + for (i = 0; i < sizeof(expected); i++) { + expected[i] = (uint8_t)(lhs[i] + rhs[i]); + } + + OK(uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_LITTLE_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_MIPS32_P5600)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, enable_code, sizeof(enable_code))); + OK(uc_reg_read(uc, UC_MIPS_REG_CP0_STATUS, &status)); + status |= (1u << 29) | (1u << 26); + OK(uc_reg_write(uc, UC_MIPS_REG_CP0_STATUS, &status)); + t0 = 1u << 27; + OK(uc_reg_write(uc, UC_MIPS_REG_T0, &t0)); + OK(uc_reg_write(uc, UC_MIPS_REG_W1, lhs)); + OK(uc_reg_write(uc, UC_MIPS_REG_W2, rhs)); + OK(uc_emu_start(uc, code_start, + code_start + sizeof(enable_code), 0, 0)); + OK(uc_reg_read(uc, UC_MIPS_REG_W3, result)); + TEST_CHECK(memcmp(result, expected, sizeof(expected)) == 0); + OK(uc_close(uc)); + + OK(uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_LITTLE_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_MIPS32_P5600)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, disable_code, sizeof(disable_code))); + OK(uc_reg_read(uc, UC_MIPS_REG_CP0_STATUS, &status)); + status |= (1u << 29) | (1u << 26); + OK(uc_reg_write(uc, UC_MIPS_REG_CP0_STATUS, &status)); + t0 = 0; + OK(uc_reg_write(uc, UC_MIPS_REG_T0, &t0)); + uc_assert_err(UC_ERR_EXCEPTION, + uc_emu_start(uc, code_start, + code_start + sizeof(disable_code), 0, 0)); + OK(uc_close(uc)); + + OK(uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_LITTLE_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_MIPS32_24KC)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, addv_code, sizeof(addv_code))); + uc_assert_err(UC_ERR_EXCEPTION, + uc_emu_start(uc, code_start, + code_start + sizeof(addv_code), 0, 0)); + OK(uc_close(uc)); +} + +static void test_mips_msa_branch_delay_slot(void) +{ + const uint32_t code[] = { + LEINT32(0x40888005), /* mtc0 t0,Config5,5 */ + LEINT32(0x45610002), /* bz.v w1,+2 */ + LEINT32(0x24020001), /* addiu v0,zero,1 */ + LEINT32(0x24030002), /* addiu v1,zero,2 */ + LEINT32(0x24040003), /* addiu a0,zero,3 */ + }; + const uint8_t zero[16] = {0}; + const uint8_t nonzero[16] = {1}; + uc_engine *uc; + uint32_t status; + uint32_t config5 = 1u << 27; + uint32_t v0 = 0; + uint32_t v1 = 0; + uint32_t a0 = 0; + + OK(uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_LITTLE_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_MIPS32_P5600)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_reg_read(uc, UC_MIPS_REG_CP0_STATUS, &status)); + status |= MIPS_CP0_STATUS_CU1 | MIPS_CP0_STATUS_FR; + OK(uc_reg_write(uc, UC_MIPS_REG_CP0_STATUS, &status)); + OK(uc_reg_write(uc, UC_MIPS_REG_T0, &config5)); + OK(uc_reg_write(uc, UC_MIPS_REG_W1, zero)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_MIPS_REG_V0, &v0)); + OK(uc_reg_read(uc, UC_MIPS_REG_V1, &v1)); + OK(uc_reg_read(uc, UC_MIPS_REG_A0, &a0)); + TEST_CHECK(v0 == 1); + TEST_CHECK(v1 == 0); + TEST_CHECK(a0 == 3); + + v0 = 0; + v1 = 0; + a0 = 0; + OK(uc_reg_write(uc, UC_MIPS_REG_V0, &v0)); + OK(uc_reg_write(uc, UC_MIPS_REG_V1, &v1)); + OK(uc_reg_write(uc, UC_MIPS_REG_A0, &a0)); + OK(uc_reg_write(uc, UC_MIPS_REG_W1, nonzero)); + OK(uc_reg_write(uc, UC_MIPS_REG_T0, &config5)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_MIPS_REG_V0, &v0)); + OK(uc_reg_read(uc, UC_MIPS_REG_V1, &v1)); + OK(uc_reg_read(uc, UC_MIPS_REG_A0, &a0)); + TEST_CHECK(v0 == 1); + TEST_CHECK(v1 == 2); + TEST_CHECK(a0 == 3); + + OK(uc_close(uc)); +} + +static void test_mips_msa_saturation(void) +{ + const uint32_t code[] = { + LEINT32(0x40888005), /* mtc0 t0,Config5,5 */ + LEINT32(0x790208d0), /* adds_s.b w3,w1,w2 */ + LEINT32(0x7873090a), /* sat_s.b w4,w1,3 */ + }; + const uint8_t lhs[16] = { + 0x78, 0x88, 0x7f, 0x80, 0x06, 0xfa, 0x08, 0xf8, + 0x01, 0xff, 0x40, 0xc0, 0x7e, 0x82, 0x00, 0x80, + }; + const uint8_t rhs[16] = { + 0x14, 0xec, 0x01, 0xff, 0x03, 0xfd, 0x01, 0xff, + 0x02, 0xfe, 0x40, 0xc0, 0x01, 0xff, 0x7f, 0x80, + }; + const uint8_t expected_add[16] = { + 0x7f, 0x80, 0x7f, 0x80, 0x09, 0xf7, 0x09, 0xf7, + 0x03, 0xfd, 0x7f, 0x80, 0x7f, 0x81, 0x7f, 0x80, + }; + const uint8_t expected_sat[16] = { + 0x07, 0xf8, 0x07, 0xf8, 0x06, 0xfa, 0x07, 0xf8, + 0x01, 0xff, 0x07, 0xf8, 0x07, 0xf8, 0x00, 0xf8, + }; + uint8_t add_result[16] = {0}; + uint8_t sat_result[16] = {0}; + uc_engine *uc; + uint32_t status; + uint32_t config5 = 1u << 27; + + OK(uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_LITTLE_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_MIPS32_P5600)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_reg_read(uc, UC_MIPS_REG_CP0_STATUS, &status)); + status |= MIPS_CP0_STATUS_CU1 | MIPS_CP0_STATUS_FR; + OK(uc_reg_write(uc, UC_MIPS_REG_CP0_STATUS, &status)); + OK(uc_reg_write(uc, UC_MIPS_REG_T0, &config5)); + OK(uc_reg_write(uc, UC_MIPS_REG_W1, lhs)); + OK(uc_reg_write(uc, UC_MIPS_REG_W2, rhs)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_MIPS_REG_W3, add_result)); + OK(uc_reg_read(uc, UC_MIPS_REG_W4, sat_result)); + TEST_CHECK(memcmp(add_result, expected_add, sizeof(expected_add)) == 0); + TEST_CHECK(memcmp(sat_result, expected_sat, sizeof(expected_sat)) == 0); + + OK(uc_close(uc)); +} + +static void test_mips_msa_fp_nan_compare(void) +{ + const uint32_t code[] = { + LEINT32(0x40888005), /* mtc0 t0,Config5,5 */ + LEINT32(0x7882095a), /* fceq.w w5,w1,w2 */ + LEINT32(0x7842099a), /* fcun.w w6,w1,w2 */ + }; + const uint32_t lhs[4] = { + 0x3f800000, + 0x7fc00000, + 0x80000000, + 0x40000000, + }; + const uint32_t rhs[4] = { + 0x3f800000, + 0x3f800000, + 0x00000000, + 0x40400000, + }; + const uint32_t expected_equal[4] = { + UINT32_MAX, + 0, + UINT32_MAX, + 0, + }; + const uint32_t expected_unordered[4] = { + 0, + UINT32_MAX, + 0, + 0, + }; + uint32_t equal[4] = {0}; + uint32_t unordered[4] = {0}; + uc_engine *uc; + uint32_t status; + uint32_t config5 = 1u << 27; + + OK(uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_LITTLE_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_MIPS32_P5600)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_reg_read(uc, UC_MIPS_REG_CP0_STATUS, &status)); + status |= MIPS_CP0_STATUS_CU1 | MIPS_CP0_STATUS_FR; + OK(uc_reg_write(uc, UC_MIPS_REG_CP0_STATUS, &status)); + OK(uc_reg_write(uc, UC_MIPS_REG_T0, &config5)); + OK(uc_reg_write(uc, UC_MIPS_REG_W1, lhs)); + OK(uc_reg_write(uc, UC_MIPS_REG_W2, rhs)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_MIPS_REG_W5, equal)); + OK(uc_reg_read(uc, UC_MIPS_REG_W6, unordered)); + TEST_CHECK(memcmp(equal, expected_equal, sizeof(expected_equal)) == 0); + TEST_CHECK( + memcmp(unordered, expected_unordered, sizeof(expected_unordered)) == 0); + + OK(uc_close(uc)); +} + +static void test_mips_msa_msacsr_rounding_and_fcsr(void) +{ + const uint32_t code[] = { + LEINT32(0x40888005), /* mtc0 t0,Config5,5 */ + LEINT32(0x783e4859), /* ctcmsa $1,t1 */ + LEINT32(0x7b38089e), /* ftint_s.w w2,w1 */ + LEINT32(0x787e0a99), /* cfcmsa t2,$1 */ + LEINT32(0x7b3820de), /* ftint_s.w w3,w4 */ + LEINT32(0x787e0ad9), /* cfcmsa t3,$1 */ + }; + const uint32_t fractional[4] = { + 0x3fc00000, + 0xbfc00000, + 0x3fa00000, + 0xbfa00000, + }; + const uint32_t exact[4] = { + 0x3f800000, + 0xc0000000, + 0x40400000, + 0xc0800000, + }; + const uint32_t expected_rounded[4] = {2, UINT32_MAX, 2, UINT32_MAX}; + const uint32_t expected_exact[4] = {1, UINT32_MAX - 1, 3, + UINT32_MAX - 3}; + uint32_t rounded[4] = {0}; + uint32_t exact_result[4] = {0}; + uc_engine *uc; + uint32_t status; + uint32_t config5 = 1u << 27; + uint32_t initial_msacsr = MIPS_MSACSR_RM_UP; + uint32_t msacsr_after_inexact = 0; + uint32_t msacsr_after_exact = 0; + uint32_t fcsr = MIPS_FCSR_FS | MIPS_FCSR_RM_DOWN; + uint32_t fcsr_before = 0; + uint32_t fcsr_after = 0; + + OK(uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_LITTLE_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_MIPS32_P5600)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_reg_read(uc, UC_MIPS_REG_CP0_STATUS, &status)); + status |= MIPS_CP0_STATUS_CU1 | MIPS_CP0_STATUS_FR; + OK(uc_reg_write(uc, UC_MIPS_REG_CP0_STATUS, &status)); + OK(uc_reg_write(uc, UC_MIPS_REG_FCSR, &fcsr)); + OK(uc_reg_read(uc, UC_MIPS_REG_FCSR, &fcsr_before)); + OK(uc_reg_write(uc, UC_MIPS_REG_T0, &config5)); + OK(uc_reg_write(uc, UC_MIPS_REG_T1, &initial_msacsr)); + OK(uc_reg_write(uc, UC_MIPS_REG_W1, fractional)); + OK(uc_reg_write(uc, UC_MIPS_REG_W4, exact)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_MIPS_REG_W2, rounded)); + OK(uc_reg_read(uc, UC_MIPS_REG_W3, exact_result)); + OK(uc_reg_read(uc, UC_MIPS_REG_T2, &msacsr_after_inexact)); + OK(uc_reg_read(uc, UC_MIPS_REG_T3, &msacsr_after_exact)); + OK(uc_reg_read(uc, UC_MIPS_REG_FCSR, &fcsr_after)); + TEST_CHECK(memcmp(rounded, expected_rounded, sizeof(rounded)) == 0); + TEST_CHECK(memcmp(exact_result, expected_exact, sizeof(exact_result)) == + 0); + TEST_CHECK_(msacsr_after_inexact == + (MIPS_MSACSR_RM_UP | MIPS_MSACSR_FLAG_INEXACT | + MIPS_MSACSR_CAUSE_INEXACT), + "msacsr_after_inexact=0x%08x", msacsr_after_inexact); + TEST_CHECK_(msacsr_after_exact == + (MIPS_MSACSR_RM_UP | MIPS_MSACSR_FLAG_INEXACT), + "msacsr_after_exact=0x%08x", msacsr_after_exact); + TEST_CHECK((fcsr_before & (MIPS_FCSR_FS | MIPS_FCSR_RM_DOWN)) == + (MIPS_FCSR_FS | MIPS_FCSR_RM_DOWN)); + TEST_CHECK(fcsr_after == fcsr_before); + + OK(uc_close(uc)); +} + +static void test_mips_msa_msacsr_flush_to_zero(void) +{ + const uint32_t code[] = { + LEINT32(0x40888005), /* mtc0 t0,Config5,5 */ + LEINT32(0x783e4859), /* ctcmsa $1,t1 */ + LEINT32(0x780208db), /* fadd.w w3,w1,w2 */ + LEINT32(0x787e0a99), /* cfcmsa t2,$1 */ + }; + const uint32_t subnormal[4] = {1, 0x80000001, 2, 0x80000002}; + const uint32_t zero[4] = {0}; + const uint32_t expected_result[2][4] = { + {1, 0x80000001, 2, 0x80000002}, + {0, 0, 0, 0}, + }; + const uint32_t initial_msacsr[2] = {0, MIPS_MSACSR_FS}; + const uint32_t expected_msacsr[2] = { + 0, + MIPS_MSACSR_FS | MIPS_MSACSR_CAUSE_INEXACT | + MIPS_MSACSR_FLAG_INEXACT, + }; + uint32_t result[4]; + uc_engine *uc; + uint32_t status; + uint32_t config5 = 1u << 27; + uint32_t msacsr; + size_t i; + + for (i = 0; i < 2; i++) { + memset(result, 0, sizeof(result)); + msacsr = 0; + OK(uc_open(UC_ARCH_MIPS, + UC_MODE_MIPS32 | UC_MODE_LITTLE_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_MIPS32_P5600)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_reg_read(uc, UC_MIPS_REG_CP0_STATUS, &status)); + status |= MIPS_CP0_STATUS_CU1 | MIPS_CP0_STATUS_FR; + OK(uc_reg_write(uc, UC_MIPS_REG_CP0_STATUS, &status)); + OK(uc_reg_write(uc, UC_MIPS_REG_T0, &config5)); + OK(uc_reg_write(uc, UC_MIPS_REG_T1, &initial_msacsr[i])); + OK(uc_reg_write(uc, UC_MIPS_REG_W1, subnormal)); + OK(uc_reg_write(uc, UC_MIPS_REG_W2, zero)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_MIPS_REG_W3, result)); + OK(uc_reg_read(uc, UC_MIPS_REG_T2, &msacsr)); + TEST_CHECK_(memcmp(result, expected_result[i], sizeof(result)) == 0, + "case=%u result=%08x:%08x:%08x:%08x", + (unsigned int)i, result[0], result[1], result[2], + result[3]); + TEST_CHECK_(msacsr == expected_msacsr[i], "msacsr=0x%08x", msacsr); + + OK(uc_close(uc)); + } +} + +static void test_mips_dsp_arithmetic_control(void) +{ + uc_engine *uc; + uint32_t control = 0x0b004aaa; + uint32_t control_result = 0; + uint32_t packed_lhs = 0xff00ff80; + uint32_t packed_rhs = 0x02030190; + uint32_t add_lhs = UINT32_MAX; + uint32_t add_rhs = 1; + uint32_t packed_result = 0; + uint32_t saturated_result = 0; + uint32_t add_result = UINT32_MAX; + uint32_t flags = 0; + uint32_t code[] = { + BEINT32(0x7d01fcf8), /* wrdsp t0, 0x3f */ + BEINT32(0x7c3f4cb8), /* rddsp t1, 0x3f */ + BEINT32(0x7d4b6010), /* addu.qb t4, t2, t3 */ + BEINT32(0x7d4b6910), /* addu_s.qb t5, t2, t3 */ + BEINT32(0x7dcf8410), /* addsc s0, t6, t7 */ + BEINT32(0x7c0c8cb8), /* rddsp s1, 0x0c */ + }; + + OK(uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_MIPS32_74KF)); + enable_mips32_dsp(uc); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_reg_write(uc, UC_MIPS_REG_T0, &control)); + OK(uc_reg_write(uc, UC_MIPS_REG_T2, &packed_lhs)); + OK(uc_reg_write(uc, UC_MIPS_REG_T3, &packed_rhs)); + OK(uc_reg_write(uc, UC_MIPS_REG_T6, &add_lhs)); + OK(uc_reg_write(uc, UC_MIPS_REG_T7, &add_rhs)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_MIPS_REG_T1, &control_result)); + OK(uc_reg_read(uc, UC_MIPS_REG_T4, &packed_result)); + OK(uc_reg_read(uc, UC_MIPS_REG_T5, &saturated_result)); + OK(uc_reg_read(uc, UC_MIPS_REG_S0, &add_result)); + OK(uc_reg_read(uc, UC_MIPS_REG_S1, &flags)); + + TEST_CHECK(control_result == control); + TEST_CHECK(packed_result == 0x01030010); + TEST_CHECK(saturated_result == 0xff03ffff); + TEST_CHECK(add_result == 0); + TEST_CHECK(flags == ((1u << 20) | (1u << 13))); + + OK(uc_close(uc)); +} + +static void test_mips_dsp_accumulator(void) +{ + uc_engine *uc; + uint32_t acc_hi = 1; + uint32_t acc_lo = 0xfffffffe; + uint32_t lhs = 0x00020003; + uint32_t rhs = 0x00040005; + uint32_t result_hi = 0; + uint32_t result_lo = 0; + uint32_t code[] = { + BEINT32(0x01000811), /* mthi t0, ac1 */ + BEINT32(0x01200813), /* mtlo t1, ac1 */ + BEINT32(0x7d4b0830), /* dpa.w.ph ac1, t2, t3 */ + BEINT32(0x00206010), /* mfhi t4, ac1 */ + BEINT32(0x00206812), /* mflo t5, ac1 */ + }; + + OK(uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_MIPS32_74KF)); + enable_mips32_dsp(uc); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_reg_write(uc, UC_MIPS_REG_T0, &acc_hi)); + OK(uc_reg_write(uc, UC_MIPS_REG_T1, &acc_lo)); + OK(uc_reg_write(uc, UC_MIPS_REG_T2, &lhs)); + OK(uc_reg_write(uc, UC_MIPS_REG_T3, &rhs)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_MIPS_REG_T4, &result_hi)); + OK(uc_reg_read(uc, UC_MIPS_REG_T5, &result_lo)); + TEST_CHECK(result_hi == 2); + TEST_CHECK(result_lo == 0x15); + + OK(uc_close(uc)); +} + +static void test_mips_dsp_compare_pick(void) +{ + const uint32_t code[] = { + BEINT32(0x7d4b0211), /* cmp.eq.ph t2,t3 */ + BEINT32(0x7d4b62d1), /* pick.ph t4,t2,t3 */ + BEINT32(0x7c106cb8), /* rddsp t5,0x10 */ + }; + uc_engine *uc; + uint32_t lhs = 0x11112222; + uint32_t rhs = 0x11113333; + uint32_t picked = 0; + uint32_t condition = 0; + + OK(uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_MIPS32_74KF)); + enable_mips32_dsp(uc); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_reg_write(uc, UC_MIPS_REG_T2, &lhs)); + OK(uc_reg_write(uc, UC_MIPS_REG_T3, &rhs)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_MIPS_REG_T4, &picked)); + OK(uc_reg_read(uc, UC_MIPS_REG_T5, &condition)); + TEST_CHECK_(picked == 0x11113333, "picked=0x%08x", picked); + TEST_CHECK_(condition == 0x02000000, "condition=0x%08x", condition); + + OK(uc_close(uc)); +} + +static void test_mips_dsp_requires_dsp_model(void) +{ + uc_engine *uc; + uint32_t lhs = 0x01020304; + uint32_t rhs = 0x05060708; + uint32_t code[] = { + BEINT32(0x7d4b6010), /* addu.qb t4, t2, t3 */ + }; + + OK(uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_MIPS32_24KC)); + enable_mips32_dsp(uc); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_reg_write(uc, UC_MIPS_REG_T2, &lhs)); + OK(uc_reg_write(uc, UC_MIPS_REG_T3, &rhs)); + + uc_assert_err(UC_ERR_EXCEPTION, + uc_emu_start(uc, code_start, + code_start + sizeof(code), 0, 0)); + + OK(uc_close(uc)); +} + static void test_mips_mips_fpr(void) { uc_engine *uc; @@ -1067,6 +1925,14 @@ static void test_mips_cp0_count_compare(void) TEST_LIST = { {"test_mips_stop_at_branch", test_mips_stop_at_branch}, {"test_mips_stop_at_delay_slot", test_mips_stop_at_delay_slot}, + {"test_mips_delay_slot_pending_stop", + test_mips_delay_slot_pending_stop}, + {"test_mips_big_endian_delay_slot_callback_smc", + test_mips_big_endian_delay_slot_callback_smc}, + {"test_mips_big_endian_delay_slot_memory_hook_pc", + test_mips_big_endian_delay_slot_memory_hook_pc}, + {"test_mips_not_taken_branch_likely_code_hook", + test_mips_not_taken_branch_likely_code_hook}, {"test_mips_el_ori", test_mips_el_ori}, {"test_mips_eb_ori", test_mips_eb_ori}, {"test_mips_lwx_exception_issue_1314", test_mips_lwx_exception_issue_1314}, @@ -1076,36 +1942,46 @@ TEST_LIST = { {"test_mips_nanomips_model_move16", test_mips_nanomips_model_move16}, {"test_mips_mips3_mode_opens", test_mips_mips3_mode_opens}, {"test_mips_msa_w_reg_roundtrip", test_mips_msa_w_reg_roundtrip}, + {"test_mips_msa_addv_b", test_mips_msa_addv_b}, + {"test_mips_msa_branch_delay_slot", test_mips_msa_branch_delay_slot}, + {"test_mips_msa_saturation", test_mips_msa_saturation}, + {"test_mips_msa_fp_nan_compare", test_mips_msa_fp_nan_compare}, + {"test_mips_msa_msacsr_rounding_and_fcsr", + test_mips_msa_msacsr_rounding_and_fcsr}, + {"test_mips_msa_msacsr_flush_to_zero", + test_mips_msa_msacsr_flush_to_zero}, + {"test_mips_dsp_arithmetic_control", test_mips_dsp_arithmetic_control}, + {"test_mips_dsp_accumulator", test_mips_dsp_accumulator}, + {"test_mips_dsp_compare_pick", test_mips_dsp_compare_pick}, + {"test_mips_dsp_requires_dsp_model", test_mips_dsp_requires_dsp_model}, {"test_mips_mips_fpr", test_mips_mips_fpr}, {"test_mips_stop_delay_slot_from_qiling", test_mips_stop_delay_slot_from_qiling}, - {"test_mips_simple_coredump_2134", test_mips_simple_coredump_2134}, - {"test_mips_simple_coredump_2137", test_mips_simple_coredump_2137}, - {"test_mips64_loongson2f_status", test_mips64_loongson2f_status}, - {"test_mips64_loongson3a_dmult", test_mips64_loongson3a_dmult}, - {"test_mips64_loongson3a_requires_lext", - test_mips64_loongson3a_requires_lext}, - {"test_mips64_loongson3a_load_zero_prefetch", - test_mips64_loongson3a_load_zero_prefetch}, - {"test_mips64_loongson3a_lext_lsdc2_gpr", - test_mips64_loongson3a_lext_lsdc2_gpr}, - {"test_mips64_loongson3a_lext_lsdc2_requires_lext", - test_mips64_loongson3a_lext_lsdc2_requires_lext}, - {"test_mips64_loongson3a_lext_gslsq_gpr", - test_mips64_loongson3a_lext_gslsq_gpr}, - {"test_mips64_loongson3a_lext_gslsq_requires_lext", - test_mips64_loongson3a_lext_gslsq_requires_lext}, - {"test_mips64_loongson3a_lext_lsdc2_fpr", - test_mips64_loongson3a_lext_lsdc2_fpr}, - {"test_mips64_loongson3a_lext_gslsq_fpr", - test_mips64_loongson3a_lext_gslsq_fpr}, - {"test_mips64_loongson3a_lext_shifted_fpr", - test_mips64_loongson3a_lext_shifted_fpr}, - {"test_mips64_loongson3a_pagemask", - test_mips64_loongson3a_pagemask}, - {"test_mips64_octeon_arithmetic", test_mips64_octeon_arithmetic}, - {"test_mips64_octeon_bbit", test_mips64_octeon_bbit}, - {"test_mips64_octeon_requires_octeon", - test_mips64_octeon_requires_octeon}, - {"test_mips_cp0_count_compare", test_mips_cp0_count_compare}, + {"test_mips_simple_coredump_2134", test_mips_simple_coredump_2134}, + {"test_mips_simple_coredump_2137", test_mips_simple_coredump_2137}, + {"test_mips64_loongson2f_status", test_mips64_loongson2f_status}, + {"test_mips64_loongson3a_dmult", test_mips64_loongson3a_dmult}, + {"test_mips64_loongson3a_requires_lext", + test_mips64_loongson3a_requires_lext}, + {"test_mips64_loongson3a_load_zero_prefetch", + test_mips64_loongson3a_load_zero_prefetch}, + {"test_mips64_loongson3a_lext_lsdc2_gpr", + test_mips64_loongson3a_lext_lsdc2_gpr}, + {"test_mips64_loongson3a_lext_lsdc2_requires_lext", + test_mips64_loongson3a_lext_lsdc2_requires_lext}, + {"test_mips64_loongson3a_lext_gslsq_gpr", + test_mips64_loongson3a_lext_gslsq_gpr}, + {"test_mips64_loongson3a_lext_gslsq_requires_lext", + test_mips64_loongson3a_lext_gslsq_requires_lext}, + {"test_mips64_loongson3a_lext_lsdc2_fpr", + test_mips64_loongson3a_lext_lsdc2_fpr}, + {"test_mips64_loongson3a_lext_gslsq_fpr", + test_mips64_loongson3a_lext_gslsq_fpr}, + {"test_mips64_loongson3a_lext_shifted_fpr", + test_mips64_loongson3a_lext_shifted_fpr}, + {"test_mips64_loongson3a_pagemask", test_mips64_loongson3a_pagemask}, + {"test_mips64_octeon_arithmetic", test_mips64_octeon_arithmetic}, + {"test_mips64_octeon_bbit", test_mips64_octeon_bbit}, + {"test_mips64_octeon_requires_octeon", test_mips64_octeon_requires_octeon}, + {"test_mips_cp0_count_compare", test_mips_cp0_count_compare}, {NULL, NULL}}; diff --git a/tests/unit/test_ppc.c b/tests/unit/test_ppc.c index 9b8e172365..4c97d7006f 100644 --- a/tests/unit/test_ppc.c +++ b/tests/unit/test_ppc.c @@ -3,25 +3,55 @@ const uint64_t code_start = 0x1000; const uint64_t code_len = 0x4000; -typedef struct PpcCodeHookTrace { - uint64_t address[2]; - uint32_t size[2]; +typedef enum PpcHookEventType { + PPC_HOOK_EVENT_FETCH, + PPC_HOOK_EVENT_CODE, +} PpcHookEventType; + +typedef struct PpcHookTrace { + PpcHookEventType type[4]; + uint64_t address[4]; + uint32_t size[4]; uint32_t count; -} PpcCodeHookTrace; + uint32_t code_count; + uint32_t fetch_count; +} PpcHookTrace; -static void test_ppc64_prefixed_code_hook(uc_engine *uc, uint64_t address, - uint32_t size, void *user_data) +static void test_ppc64_hook_record(PpcHookTrace *trace, + PpcHookEventType type, uint64_t address, + uint32_t size) { - PpcCodeHookTrace *trace = (PpcCodeHookTrace *)user_data; - - (void)uc; - if (trace->count < 2) { + if (TEST_CHECK(trace->count < 4)) { + trace->type[trace->count] = type; trace->address[trace->count] = address; trace->size[trace->count] = size; } trace->count++; } +static void test_ppc64_prefixed_code_hook(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + PpcHookTrace *trace = (PpcHookTrace *)user_data; + + (void)uc; + trace->code_count++; + test_ppc64_hook_record(trace, PPC_HOOK_EVENT_CODE, address, size); +} + +static void test_ppc64_prefixed_fetch_hook(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + PpcHookTrace *trace = (PpcHookTrace *)user_data; + + (void)uc; + TEST_CHECK(type == UC_MEM_FETCH); + TEST_CHECK(value == 0); + trace->fetch_count++; + test_ppc64_hook_record(trace, PPC_HOOK_EVENT_FETCH, address, size); +} + static void uc_common_setup(uc_engine **uc, uc_arch arch, uc_mode mode, const char *code, uint64_t size) { @@ -53,6 +83,288 @@ static void test_ppc32_add(void) OK(uc_close(uc)); } +static void test_ppc_engine_lifecycle(void) +{ + const uc_mode modes[] = { UC_MODE_32, UC_MODE_64 }; + size_t i; + size_t j; + + for (i = 0; i < 16; i++) { + for (j = 0; j < sizeof(modes) / sizeof(modes[0]); j++) { + uc_engine *uc; + + OK(uc_open(UC_ARCH_PPC, modes[j] | UC_MODE_BIG_ENDIAN, &uc)); + if (modes[j] == UC_MODE_32) { + uint32_t pc; + + OK(uc_reg_read(uc, UC_PPC_REG_PC, &pc)); + TEST_CHECK(pc == 0); + } else { + uint64_t pc; + + OK(uc_reg_read(uc, UC_PPC_REG_PC, &pc)); + TEST_CHECK(pc == 0); + } + OK(uc_close(uc)); + } + } +} + +static void run_ppc_instruction_count(uc_mode mode) +{ + static const size_t counts[] = { + 1, 2, 3, + 32766, 32767, 32768, 32769, + 65534, 65535, 65536, 65537, + 70000, + }; + const char code[] = + "\x38\x63\x00\x01" /* addi r3,r3,1 */ + "\x4b\xff\xff\xfc"; /* b .-4 */ + uc_engine *uc; + size_t i; + + uc_common_setup(&uc, UC_ARCH_PPC, mode | UC_MODE_BIG_ENDIAN, + code, sizeof(code) - 1); + + for (i = 0; i < sizeof(counts) / sizeof(counts[0]); i++) { + uint64_t expected = (counts[i] + 1) / 2; + + if (mode == UC_MODE_64) { + uint64_t pc; + uint64_t r3 = 0; + + OK(uc_reg_write(uc, UC_PPC_REG_3, &r3)); + OK(uc_emu_start(uc, code_start, 0, 0, counts[i])); + OK(uc_reg_read(uc, UC_PPC_REG_3, &r3)); + OK(uc_reg_read(uc, UC_PPC_REG_PC, &pc)); + TEST_CHECK_(r3 == expected, "count=%zu r3=%llu", + counts[i], (unsigned long long)r3); + TEST_CHECK_(pc == code_start + (counts[i] & 1) * 4, + "count=%zu pc=0x%llx", counts[i], + (unsigned long long)pc); + } else { + uint32_t pc; + uint32_t r3 = 0; + + OK(uc_reg_write(uc, UC_PPC_REG_3, &r3)); + OK(uc_emu_start(uc, code_start, 0, 0, counts[i])); + OK(uc_reg_read(uc, UC_PPC_REG_3, &r3)); + OK(uc_reg_read(uc, UC_PPC_REG_PC, &pc)); + TEST_CHECK_(r3 == expected, "count=%zu r3=%u", + counts[i], r3); + TEST_CHECK_(pc == code_start + (counts[i] & 1) * 4, + "count=%zu pc=0x%x", counts[i], pc); + } + } + + OK(uc_close(uc)); +} + +static void test_ppc32_instruction_count_boundary(void) +{ + run_ppc_instruction_count(UC_MODE_32); +} + +static void test_ppc64_instruction_count_boundary(void) +{ + run_ppc_instruction_count(UC_MODE_64); +} + +static void test_ppc32_reservation(void) +{ + const uint64_t data_address = 0x8000; + const uint64_t other_address = 0x9000; + const char code[] = + "\x7c\x60\x20\x28" /* lwarx r3,0,r4 */ + "\x7c\xa0\x21\x2d" /* stwcx. r5,0,r4 */ + "\x7c\xc0\x21\x2d" /* stwcx. r6,0,r4 */ + "\x7c\x60\x20\x28" /* lwarx r3,0,r4 */ + "\x7c\xa0\x39\x2d" /* stwcx. r5,0,r7 */ + "\x7c\xc0\x21\x2d"; /* stwcx. r6,0,r4 */ + const uint8_t initial[] = { 0x11, 0x22, 0x33, 0x44 }; + const uint8_t other[] = { 0x55, 0x66, 0x77, 0x88 }; + const uint8_t stored[] = { 0xaa, 0xbb, 0xcc, 0xdd }; + uint8_t memory[sizeof(initial)]; + uint32_t r3; + uint32_t r4 = (uint32_t)data_address; + uint32_t r5 = 0xaabbccdd; + uint32_t r6 = 0xdeadbeef; + uint32_t r7 = (uint32_t)other_address; + uint32_t cr0; + uint32_t xer = 0; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_PPC, UC_MODE_32 | UC_MODE_BIG_ENDIAN, + code, sizeof(code) - 1); + OK(uc_mem_map(uc, data_address, 0x2000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, initial, sizeof(initial))); + OK(uc_mem_write(uc, other_address, other, sizeof(other))); + OK(uc_reg_write(uc, UC_PPC_REG_4, &r4)); + OK(uc_reg_write(uc, UC_PPC_REG_5, &r5)); + OK(uc_reg_write(uc, UC_PPC_REG_6, &r6)); + OK(uc_reg_write(uc, UC_PPC_REG_7, &r7)); + OK(uc_reg_write(uc, UC_PPC_REG_XER, &xer)); + + OK(uc_emu_start(uc, code_start, 0, 0, 2)); + OK(uc_reg_read(uc, UC_PPC_REG_3, &r3)); + OK(uc_reg_read(uc, UC_PPC_REG_CR0, &cr0)); + OK(uc_mem_read(uc, data_address, memory, sizeof(memory))); + TEST_CHECK(r3 == 0x11223344); + TEST_CHECK(cr0 == 0x2); + TEST_CHECK(memcmp(memory, stored, sizeof(memory)) == 0); + + OK(uc_emu_start(uc, code_start + 8, 0, 0, 1)); + OK(uc_reg_read(uc, UC_PPC_REG_CR0, &cr0)); + OK(uc_mem_read(uc, data_address, memory, sizeof(memory))); + TEST_CHECK(cr0 == 0); + TEST_CHECK(memcmp(memory, stored, sizeof(memory)) == 0); + + OK(uc_emu_start(uc, code_start + 12, 0, 0, 2)); + OK(uc_reg_read(uc, UC_PPC_REG_3, &r3)); + OK(uc_reg_read(uc, UC_PPC_REG_CR0, &cr0)); + OK(uc_mem_read(uc, other_address, memory, sizeof(memory))); + TEST_CHECK(r3 == 0xaabbccdd); + TEST_CHECK(cr0 == 0); + TEST_CHECK(memcmp(memory, other, sizeof(memory)) == 0); + + OK(uc_emu_start(uc, code_start + 20, 0, 0, 1)); + OK(uc_reg_read(uc, UC_PPC_REG_CR0, &cr0)); + OK(uc_mem_read(uc, data_address, memory, sizeof(memory))); + TEST_CHECK(cr0 == 0); + TEST_CHECK(memcmp(memory, stored, sizeof(memory)) == 0); + + OK(uc_close(uc)); +} + +static void test_ppc64_reservation(void) +{ + const uint64_t data_address = 0x8000; + const uint64_t other_address = 0x9000; + const char code[] = + "\x7c\x60\x20\xa8" /* ldarx r3,0,r4 */ + "\x7c\xa0\x21\xad" /* stdcx. r5,0,r4 */ + "\x7c\xc0\x21\xad" /* stdcx. r6,0,r4 */ + "\x7c\x60\x20\xa8" /* ldarx r3,0,r4 */ + "\x7c\xa0\x39\xad" /* stdcx. r5,0,r7 */ + "\x7c\xc0\x21\xad"; /* stdcx. r6,0,r4 */ + const uint8_t initial[] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + }; + const uint8_t other[] = { + 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, + }; + const uint8_t stored[] = { + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, + }; + uint8_t memory[sizeof(initial)]; + uint64_t r3; + uint64_t r4 = data_address; + uint64_t r5 = 0xaabbccddeeff0011ull; + uint64_t r6 = 0xdeadbeefcafebabeull; + uint64_t r7 = other_address; + uint32_t cr0; + uint32_t xer = 0; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_PPC, UC_MODE_64 | UC_MODE_BIG_ENDIAN, + code, sizeof(code) - 1); + OK(uc_mem_map(uc, data_address, 0x2000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, initial, sizeof(initial))); + OK(uc_mem_write(uc, other_address, other, sizeof(other))); + OK(uc_reg_write(uc, UC_PPC_REG_4, &r4)); + OK(uc_reg_write(uc, UC_PPC_REG_5, &r5)); + OK(uc_reg_write(uc, UC_PPC_REG_6, &r6)); + OK(uc_reg_write(uc, UC_PPC_REG_7, &r7)); + OK(uc_reg_write(uc, UC_PPC_REG_XER, &xer)); + + OK(uc_emu_start(uc, code_start, 0, 0, 2)); + OK(uc_reg_read(uc, UC_PPC_REG_3, &r3)); + OK(uc_reg_read(uc, UC_PPC_REG_CR0, &cr0)); + OK(uc_mem_read(uc, data_address, memory, sizeof(memory))); + TEST_CHECK(r3 == 0x0123456789abcdefull); + TEST_CHECK(cr0 == 0x2); + TEST_CHECK(memcmp(memory, stored, sizeof(memory)) == 0); + + OK(uc_emu_start(uc, code_start + 8, 0, 0, 1)); + OK(uc_reg_read(uc, UC_PPC_REG_CR0, &cr0)); + OK(uc_mem_read(uc, data_address, memory, sizeof(memory))); + TEST_CHECK(cr0 == 0); + TEST_CHECK(memcmp(memory, stored, sizeof(memory)) == 0); + + OK(uc_emu_start(uc, code_start + 12, 0, 0, 2)); + OK(uc_reg_read(uc, UC_PPC_REG_3, &r3)); + OK(uc_reg_read(uc, UC_PPC_REG_CR0, &cr0)); + OK(uc_mem_read(uc, other_address, memory, sizeof(memory))); + TEST_CHECK(r3 == 0xaabbccddeeff0011ull); + TEST_CHECK(cr0 == 0); + TEST_CHECK(memcmp(memory, other, sizeof(memory)) == 0); + + OK(uc_emu_start(uc, code_start + 20, 0, 0, 1)); + OK(uc_reg_read(uc, UC_PPC_REG_CR0, &cr0)); + OK(uc_mem_read(uc, data_address, memory, sizeof(memory))); + TEST_CHECK(cr0 == 0); + TEST_CHECK(memcmp(memory, stored, sizeof(memory)) == 0); + + OK(uc_close(uc)); +} + +typedef struct PpcRestoreCacheHookData { + uint32_t expected_pc; + uint32_t count; +} PpcRestoreCacheHookData; + +static void test_ppc_restore_cache_memory_hook(uc_engine *uc, + uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + PpcRestoreCacheHookData *data = + (PpcRestoreCacheHookData *)user_data; + uint32_t pc; + + OK(uc_reg_read(uc, UC_PPC_REG_PC, &pc)); + TEST_CHECK_(pc == data->expected_pc, "pc=0x%x", pc); + data->count++; +} + +static void test_ppc32_memory_hook_restore_cache(void) +{ + const uint64_t data_address = 0x200000; + const char code[] = "\x80\x64\x00\x00"; /* lwz r3, 0(r4) */ + const uint32_t expected = 0x11223344; + uint32_t memory_value = BEINT32(expected); + uint32_t r3; + uint32_t r4 = (uint32_t)data_address; + PpcRestoreCacheHookData data = { + .expected_pc = code_start, + }; + size_t i; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_PPC, UC_MODE_32 | UC_MODE_BIG_ENDIAN, + code, sizeof(code) - 1); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, &memory_value, + sizeof(memory_value))); + OK(uc_reg_write(uc, UC_PPC_REG_4, &r4)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, + test_ppc_restore_cache_memory_hook, &data, + data_address, data_address + sizeof(memory_value) - 1)); + + for (i = 0; i < 8; i++) { + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, + 0, 0)); + } + + OK(uc_reg_read(uc, UC_PPC_REG_3, &r3)); + TEST_CHECK(r3 == expected); + TEST_CHECK(data.count == 8); + + OK(uc_close(uc)); +} + /* IBM AIX fadd/floating-add instruction reference. */ static void test_ppc32_fadd(void) { @@ -166,6 +478,162 @@ static void test_ppc32_cr(void) OK(uc_close(uc)); } +static void ppc32_spe_setup(uc_engine **uc, uc_cpu_ppc cpu_model, + const char *code, size_t code_size) +{ + uint32_t msr; + + OK(uc_open(UC_ARCH_PPC, UC_MODE_32 | UC_MODE_BIG_ENDIAN, uc)); + OK(uc_ctl_set_cpu_model(*uc, cpu_model)); + OK(uc_mem_map(*uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(*uc, code_start, code, code_size)); + + OK(uc_reg_read(*uc, UC_PPC_REG_MSR, &msr)); + msr |= 1u << 25; /* MSR[SPE] */ + OK(uc_reg_write(*uc, UC_PPC_REG_MSR, &msr)); + OK(uc_reg_read(*uc, UC_PPC_REG_MSR, &msr)); + TEST_CHECK((msr & (1u << 25)) != 0); +} + +static void test_ppc32_spe_vector_arithmetic_and_cr(void) +{ + const uint64_t data_address = 0x8000; + const char code[] = + "\x10\x83\x03\x01" /* evldd r4,0(r3) */ + "\x10\xa3\x0b\x01" /* evldd r5,8(r3) */ + "\x10\xc4\x2a\x00" /* evaddw r6,r4,r5 */ + "\x10\xc3\x13\x21" /* evstdd r6,16(r3) */ + "\x11\x84\x2a\x31"; /* evcmpgts cr3,r4,r5 */ + const uint8_t operands[] = { + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, + }; + const uint8_t expected[] = { + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }; + uint8_t result[sizeof(expected)]; + uint32_t r3 = (uint32_t)data_address; + uint32_t r6; + uint32_t cr3; + uc_engine *uc; + + ppc32_spe_setup(&uc, UC_CPU_PPC32_E500V2_V22, code, + sizeof(code) - 1); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, operands, sizeof(operands))); + OK(uc_reg_write(uc, UC_PPC_REG_3, &r3)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, + 0, 0)); + + OK(uc_reg_read(uc, UC_PPC_REG_6, &r6)); + OK(uc_reg_read(uc, UC_PPC_REG_CR3, &cr3)); + OK(uc_mem_read(uc, data_address + sizeof(operands), result, + sizeof(result))); + TEST_CHECK_(r6 == 1, "r6=0x%08x", r6); + TEST_CHECK_(cr3 == 0xa, "cr3=0x%x", cr3); + TEST_CHECK_(memcmp(result, expected, sizeof(result)) == 0, + "result=%02x%02x%02x%02x%02x%02x%02x%02x", + result[0], result[1], result[2], result[3], + result[4], result[5], result[6], result[7]); + + OK(uc_close(uc)); +} + +static void test_ppc32_spe_enable_and_model_gating(void) +{ + const char code[] = "\x10\xc4\x2a\x00"; /* evaddw r6,r4,r5 */ + uint32_t r4 = 0xffffffff; + uint32_t r5 = 2; + uint32_t r6 = 0xdeadbeef; + uint32_t msr; + uc_engine *uc; + + OK(uc_open(UC_ARCH_PPC, UC_MODE_32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_PPC32_E500V2_V22)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code) - 1)); + OK(uc_reg_read(uc, UC_PPC_REG_MSR, &msr)); + msr &= ~(1u << 25); + OK(uc_reg_write(uc, UC_PPC_REG_MSR, &msr)); + OK(uc_reg_write(uc, UC_PPC_REG_4, &r4)); + OK(uc_reg_write(uc, UC_PPC_REG_5, &r5)); + OK(uc_reg_write(uc, UC_PPC_REG_6, &r6)); + + TEST_CHECK(uc_emu_start(uc, code_start, + code_start + sizeof(code) - 1, 0, 0) == + UC_ERR_EXCEPTION); + OK(uc_reg_read(uc, UC_PPC_REG_6, &r6)); + TEST_CHECK(r6 == 0xdeadbeef); + + OK(uc_close(uc)); + + r6 = 0xdeadbeef; + OK(uc_open(UC_ARCH_PPC, UC_MODE_32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_PPC32_E500MC)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code) - 1)); + OK(uc_reg_read(uc, UC_PPC_REG_MSR, &msr)); + msr |= 1u << 25; + OK(uc_reg_write(uc, UC_PPC_REG_MSR, &msr)); + OK(uc_reg_read(uc, UC_PPC_REG_MSR, &msr)); + TEST_CHECK((msr & (1u << 25)) == 0); + OK(uc_reg_write(uc, UC_PPC_REG_4, &r4)); + OK(uc_reg_write(uc, UC_PPC_REG_5, &r5)); + OK(uc_reg_write(uc, UC_PPC_REG_6, &r6)); + + TEST_CHECK(uc_emu_start(uc, code_start, + code_start + sizeof(code) - 1, 0, 0) == + UC_ERR_EXCEPTION); + OK(uc_reg_read(uc, UC_PPC_REG_6, &r6)); + TEST_CHECK(r6 == 0xdeadbeef); + + OK(uc_close(uc)); +} + +static void test_ppc32_spe_double_arithmetic(void) +{ + const uint64_t data_address = 0x8000; + const char code[] = + "\x10\x83\x03\x01" /* evldd r4,0(r3) */ + "\x10\xa3\x0b\x01" /* evldd r5,8(r3) */ + "\x10\xc4\x2a\xe0" /* efdadd r6,r4,r5 */ + "\x10\xc3\x13\x21"; /* evstdd r6,16(r3) */ + const uint8_t operands[] = { + 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + const uint8_t expected[] = { + 0x40, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }; + uint8_t result[sizeof(expected)]; + uint32_t r3 = (uint32_t)data_address; + uint32_t r4; + uint32_t r6; + uc_engine *uc; + + ppc32_spe_setup(&uc, UC_CPU_PPC32_E500V2_V22, code, + sizeof(code) - 1); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, operands, sizeof(operands))); + OK(uc_reg_write(uc, UC_PPC_REG_3, &r3)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, + 0, 0)); + OK(uc_reg_read(uc, UC_PPC_REG_4, &r4)); + OK(uc_reg_read(uc, UC_PPC_REG_6, &r6)); + OK(uc_mem_read(uc, data_address + sizeof(operands), result, + sizeof(result))); + TEST_CHECK(r4 == 2); + TEST_CHECK(r6 == 1); + TEST_CHECK_(memcmp(result, expected, sizeof(result)) == 0, + "result=%02x%02x%02x%02x%02x%02x%02x%02x", + result[0], result[1], result[2], result[3], + result[4], result[5], result[6], result[7]); + + OK(uc_close(uc)); +} + static void test_ppc32_spr_time(void) { char code[] = ("\x7c\x76\x02\xa6" // mfspr r3, DEC @@ -4819,6 +5287,9 @@ static void test_ppc64_power10_pnop_invalid_suffix(void) static void test_ppc64_power10_prefixed_boundary(void) { uc_engine *uc; + uc_hook code_hook; + uc_hook fetch_hook; + PpcHookTrace trace = { 0 }; uint64_t pc = 0; uint64_t start = code_start + 0x3c; const char code[] = @@ -4828,20 +5299,33 @@ static void test_ppc64_power10_prefixed_boundary(void) OK(uc_ctl_set_cpu_model(uc, UC_CPU_PPC64_POWER10_V1_0)); OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); OK(uc_mem_write(uc, start, code, sizeof(code) - 1)); + OK(uc_hook_add(uc, &code_hook, UC_HOOK_CODE, + test_ppc64_prefixed_code_hook, &trace, 1, 0)); + OK(uc_hook_add(uc, &fetch_hook, UC_HOOK_MEM_FETCH, + test_ppc64_prefixed_fetch_hook, &trace, 1, 0)); TEST_CHECK(uc_emu_start(uc, start, start + sizeof(code) - 1, 0, 0) == UC_ERR_EXCEPTION); OK(uc_reg_read(uc, UC_PPC_REG_PC, &pc)); TEST_CHECK(pc == start + sizeof(code) - 1); + TEST_CHECK(trace.fetch_count == 0); + TEST_CHECK(trace.code_count == 1); + TEST_CHECK(trace.count == 1); + TEST_CHECK(trace.type[0] == PPC_HOOK_EVENT_CODE); + TEST_CHECK(trace.address[0] == start); + TEST_CHECK(trace.size[0] == 8); + OK(uc_hook_del(uc, fetch_hook)); + OK(uc_hook_del(uc, code_hook)); OK(uc_close(uc)); } static void test_ppc64_power10_prefixed_hook_size(void) { uc_engine *uc; - uc_hook hook; - PpcCodeHookTrace trace = { 0 }; + uc_hook code_hook; + uc_hook fetch_hook; + PpcHookTrace trace = { 0 }; const char code[] = "\x07\x00\x00\x00\x60\x00\x00\x00" /* pnop */ "\x38\x60\x00\x07"; /* addi r3,0,7 */ @@ -4850,17 +5334,31 @@ static void test_ppc64_power10_prefixed_hook_size(void) OK(uc_ctl_set_cpu_model(uc, UC_CPU_PPC64_POWER10_V1_0)); OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); OK(uc_mem_write(uc, code_start, code, sizeof(code) - 1)); - OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, test_ppc64_prefixed_code_hook, - &trace, 1, 0)); + OK(uc_hook_add(uc, &code_hook, UC_HOOK_CODE, + test_ppc64_prefixed_code_hook, &trace, 1, 0)); + OK(uc_hook_add(uc, &fetch_hook, UC_HOOK_MEM_FETCH, + test_ppc64_prefixed_fetch_hook, &trace, 1, 0)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); - TEST_CHECK(trace.count == 2); + TEST_CHECK(trace.fetch_count == 2); + TEST_CHECK(trace.code_count == 2); + TEST_CHECK(trace.count == 4); + TEST_CHECK(trace.type[0] == PPC_HOOK_EVENT_FETCH); TEST_CHECK(trace.address[0] == code_start); TEST_CHECK(trace.size[0] == 8); - TEST_CHECK(trace.address[1] == code_start + 8); - TEST_CHECK(trace.size[1] == 4); - + TEST_CHECK(trace.type[1] == PPC_HOOK_EVENT_CODE); + TEST_CHECK(trace.address[1] == code_start); + TEST_CHECK(trace.size[1] == 8); + TEST_CHECK(trace.type[2] == PPC_HOOK_EVENT_FETCH); + TEST_CHECK(trace.address[2] == code_start + 8); + TEST_CHECK(trace.size[2] == 4); + TEST_CHECK(trace.type[3] == PPC_HOOK_EVENT_CODE); + TEST_CHECK(trace.address[3] == code_start + 8); + TEST_CHECK(trace.size[3] == 4); + + OK(uc_hook_del(uc, fetch_hook)); + OK(uc_hook_del(uc, code_hook)); OK(uc_close(uc)); } @@ -4952,11 +5450,26 @@ static void test_ppc64_power9_xvp_rejected(void) } TEST_LIST = {{"test_ppc32_add", test_ppc32_add}, + {"test_ppc_engine_lifecycle", test_ppc_engine_lifecycle}, + {"test_ppc32_instruction_count_boundary", + test_ppc32_instruction_count_boundary}, + {"test_ppc64_instruction_count_boundary", + test_ppc64_instruction_count_boundary}, + {"test_ppc32_reservation", test_ppc32_reservation}, + {"test_ppc64_reservation", test_ppc64_reservation}, + {"test_ppc32_memory_hook_restore_cache", + test_ppc32_memory_hook_restore_cache}, {"test_ppc32_fadd", test_ppc32_fadd}, {"test_ppc32_sc", test_ppc32_sc}, {"test_ppc32_unaligned_access_sets_dar", test_ppc32_unaligned_access_sets_dar}, {"test_ppc32_cr", test_ppc32_cr}, + {"test_ppc32_spe_vector_arithmetic_and_cr", + test_ppc32_spe_vector_arithmetic_and_cr}, + {"test_ppc32_spe_enable_and_model_gating", + test_ppc32_spe_enable_and_model_gating}, + {"test_ppc32_spe_double_arithmetic", + test_ppc32_spe_double_arithmetic}, {"test_ppc32_spr_time", test_ppc32_spr_time}, {"test_ppc32_spr_mftb", test_ppc32_spr_mftb}, {"test_ppc64_power10_brd", test_ppc64_power10_brd}, diff --git a/tests/unit/test_riscv.c b/tests/unit/test_riscv.c index e79565e0c5..56227ab1a9 100644 --- a/tests/unit/test_riscv.c +++ b/tests/unit/test_riscv.c @@ -17,9 +17,13 @@ const uint64_t riscv_data_start = 0x8000; #define RISCV_CSR_VSATP 0x280 #define RISCV_CSR_STIMECMP 0x14d #define RISCV_CSR_MSTATUS 0x300 +#define RISCV_CSR_MTINST 0x34a +#define RISCV_CSR_MTVAL2 0x34b #define RISCV_CSR_PMPCFG0 0x3a0 #define RISCV_CSR_PMPADDR0 0x3b0 #define RISCV_CSR_HSTATUS 0x600 +#define RISCV_CSR_HTVAL 0x643 +#define RISCV_CSR_HTINST 0x64a #define RISCV_CSR_HGATP 0x680 #define RISCV_CSR_TSELECT 0x7a0 #define RISCV_CSR_TDATA1 0x7a1 @@ -27,9 +31,12 @@ const uint64_t riscv_data_start = 0x8000; #define RISCV_CSR_TINFO 0x7a4 #define RISCV_MSTATUS_MXR 0x00080000ull #define RISCV_PMPCFG_R 0x01 +#define RISCV_PMPCFG_W 0x02 #define RISCV_PMPCFG_X 0x04 +#define RISCV_PMPCFG_A_TOR 0x08 #define RISCV_PMPCFG_A_NA4 0x10 #define RISCV_PMPCFG_A_NAPOT 0x18 +#define RISCV_PMPCFG_L 0x80 #define RISCV_EXCP_INST_ACCESS_FAULT 0x1 #define RISCV_EXCP_ILLEGAL_INST 0x2 #define RISCV_EXCP_BREAKPOINT 0x3 @@ -93,6 +100,26 @@ static void test_riscv_block_count_cb(uc_engine *uc, uint64_t address, (*count)++; } +typedef struct RiscvFetchTrace { + uint64_t address; + int size; + uint32_t count; +} RiscvFetchTrace; + +static void test_riscv_fetch_trace_cb(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + RiscvFetchTrace *trace = (RiscvFetchTrace *)user_data; + + (void)uc; + TEST_CHECK(type == UC_MEM_FETCH); + TEST_CHECK(value == 0); + trace->address = address; + trace->size = size; + trace->count++; +} + static void riscv32_enable_vector_state(uc_engine *uc) { uint32_t mstatus = RISCV_MSTATUS_VS_INITIAL; @@ -167,6 +194,28 @@ static void test_riscv64_nop(void) OK(uc_close(uc)); } +static void test_riscv64_fetch_invalid_compressed_size(void) +{ + const uint8_t code[] = {0x00, 0x00}; + RiscvFetchTrace trace = {0}; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_RISCV, UC_MODE_RISCV64, + (const char *)code, sizeof(code)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_FETCH, + test_riscv_fetch_trace_cb, &trace, 1, 0)); + + uc_assert_err(UC_ERR_EXCEPTION, + uc_emu_start(uc, code_start, code_start + sizeof(code), + 0, 0)); + TEST_CHECK(trace.count == 1); + TEST_CHECK(trace.address == code_start); + TEST_CHECK(trace.size == 2); + + OK(uc_close(uc)); +} + static void test_riscv32_zihintpause(void) { uc_engine *uc; @@ -1109,8 +1158,8 @@ static void test_riscv64_mmio_map(void) } static bool test_riscv_correct_address_in_small_jump_hook_callback( - uc_engine *uc, int type, uint64_t address, int size, int64_t value, - void *user_data) + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) { // Check registers uint64_t r_x5 = 0x0; @@ -1157,8 +1206,8 @@ static void test_riscv_correct_address_in_small_jump_hook(void) } static bool test_riscv_correct_address_in_long_jump_hook_callback( - uc_engine *uc, int type, uint64_t address, int size, int64_t value, - void *user_data) + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) { // Check registers uint64_t r_x5 = 0x0; @@ -1397,6 +1446,15 @@ static uint32_t riscv_encode_csr(uint32_t csr, uint32_t rs1, return riscv_encode_i(csr, rs1, funct3, rd, 0x73); } +static uint32_t riscv_encode_atomic(uint32_t funct5, uint32_t rs2, + uint32_t rs1, uint32_t width, + uint32_t rd) +{ + return ((funct5 & 0x1f) << 27) | ((rs2 & 0x1f) << 20) | + ((rs1 & 0x1f) << 15) | ((width & 0x7) << 12) | + ((rd & 0x1f) << 7) | 0x2f; +} + static uint32_t riscv_encode_k_aes(uint32_t funct5, uint32_t shamt, uint32_t rs2, uint32_t rs1, uint32_t rd) { @@ -1534,6 +1592,270 @@ static void riscv_insn_to_code(uint8_t code[4], uint32_t insn) code[3] = insn >> 24; } +static void test_riscv32_lr_sc(void) +{ + uc_engine *uc; + uint32_t insns[] = { + riscv_encode_atomic(0x02, 0, 10, 2, 5), + riscv_encode_atomic(0x03, 11, 10, 2, 6), + riscv_encode_atomic(0x03, 12, 10, 2, 7), + }; + uint8_t code[sizeof(insns)]; + uint32_t address = riscv_data_start; + uint32_t initial = 0x11223344; + uint32_t first_store = 0x55667788; + uint32_t second_store = 0xaabbccdd; + uint32_t loaded = 0; + uint32_t first_status = 1; + uint32_t second_status = 0; + uint32_t memory = 0; + size_t i; + + for (i = 0; i < sizeof(insns) / sizeof(insns[0]); i++) { + riscv_insn_to_code(&code[i * 4], insns[i]); + } + + uc_common_setup(&uc, UC_ARCH_RISCV, UC_MODE_RISCV32, + (const char *)code, sizeof(code)); + OK(uc_mem_map(uc, riscv_data_start, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, riscv_data_start, &initial, sizeof(initial))); + OK(uc_reg_write(uc, UC_RISCV_REG_A0, &address)); + OK(uc_reg_write(uc, UC_RISCV_REG_A1, &first_store)); + OK(uc_reg_write(uc, UC_RISCV_REG_A2, &second_store)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_RISCV_REG_T0, &loaded)); + OK(uc_reg_read(uc, UC_RISCV_REG_T1, &first_status)); + OK(uc_reg_read(uc, UC_RISCV_REG_T2, &second_status)); + OK(uc_mem_read(uc, riscv_data_start, &memory, sizeof(memory))); + TEST_CHECK(loaded == initial); + TEST_CHECK(first_status == 0); + TEST_CHECK(second_status == 1); + TEST_CHECK(memory == first_store); + OK(uc_close(uc)); +} + +static void test_riscv64_lr_sc(void) +{ + uc_engine *uc; + uint32_t insns[] = { + riscv_encode_atomic(0x02, 0, 10, 3, 5), + riscv_encode_atomic(0x03, 11, 10, 3, 6), + riscv_encode_atomic(0x02, 0, 10, 3, 7), + riscv_encode_atomic(0x03, 12, 13, 3, 28), + riscv_encode_atomic(0x03, 12, 10, 3, 29), + }; + uint8_t code[sizeof(insns)]; + uint64_t address = riscv_data_start; + uint64_t other_address = riscv_data_start + sizeof(uint64_t); + uint64_t initial[] = { + 0x1122334455667788ull, + 0x99aabbccddeeff00ull, + }; + uint64_t first_store = 0x0123456789abcdefull; + uint64_t second_store = 0xfedcba9876543210ull; + uint64_t loaded = 0; + uint64_t reloaded = 0; + uint64_t success_status = 1; + uint64_t mismatch_status = 0; + uint64_t second_status = 0; + uint64_t memory[2] = { 0 }; + size_t i; + + for (i = 0; i < sizeof(insns) / sizeof(insns[0]); i++) { + riscv_insn_to_code(&code[i * 4], insns[i]); + } + + uc_common_setup(&uc, UC_ARCH_RISCV, UC_MODE_RISCV64, + (const char *)code, sizeof(code)); + OK(uc_mem_map(uc, riscv_data_start, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, riscv_data_start, initial, sizeof(initial))); + OK(uc_reg_write(uc, UC_RISCV_REG_A0, &address)); + OK(uc_reg_write(uc, UC_RISCV_REG_A1, &first_store)); + OK(uc_reg_write(uc, UC_RISCV_REG_A2, &second_store)); + OK(uc_reg_write(uc, UC_RISCV_REG_A3, &other_address)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_RISCV_REG_T0, &loaded)); + OK(uc_reg_read(uc, UC_RISCV_REG_T1, &success_status)); + OK(uc_reg_read(uc, UC_RISCV_REG_T2, &reloaded)); + OK(uc_reg_read(uc, UC_RISCV_REG_T3, &mismatch_status)); + OK(uc_reg_read(uc, UC_RISCV_REG_T4, &second_status)); + OK(uc_mem_read(uc, riscv_data_start, memory, sizeof(memory))); + TEST_CHECK(loaded == initial[0]); + TEST_CHECK(success_status == 0); + TEST_CHECK(reloaded == first_store); + TEST_CHECK(mismatch_status == 1); + TEST_CHECK(second_status == 1); + TEST_CHECK(memory[0] == first_store); + TEST_CHECK(memory[1] == initial[1]); + OK(uc_close(uc)); +} + +static void test_riscv32_amo(void) +{ + uc_engine *uc; + uint32_t insns[] = { + riscv_encode_atomic(0x00, 11, 10, 2, 5), + riscv_encode_atomic(0x01, 12, 10, 2, 6), + riscv_encode_atomic(0x10, 13, 10, 2, 7), + riscv_encode_atomic(0x18, 14, 10, 2, 28), + }; + uint8_t code[sizeof(insns)]; + uint32_t address = riscv_data_start; + uint32_t initial = 10; + uint32_t addend = 5; + uint32_t swap = 20; + uint32_t signed_min = 0xfffffffd; + uint32_t unsigned_min = 2; + uint32_t old_add = 0; + uint32_t old_swap = 0; + uint32_t old_signed_min = 0; + uint32_t old_unsigned_min = 0; + uint32_t memory = 0; + size_t i; + + for (i = 0; i < sizeof(insns) / sizeof(insns[0]); i++) { + riscv_insn_to_code(&code[i * 4], insns[i]); + } + + uc_common_setup(&uc, UC_ARCH_RISCV, UC_MODE_RISCV32, + (const char *)code, sizeof(code)); + OK(uc_mem_map(uc, riscv_data_start, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, riscv_data_start, &initial, sizeof(initial))); + OK(uc_reg_write(uc, UC_RISCV_REG_A0, &address)); + OK(uc_reg_write(uc, UC_RISCV_REG_A1, &addend)); + OK(uc_reg_write(uc, UC_RISCV_REG_A2, &swap)); + OK(uc_reg_write(uc, UC_RISCV_REG_A3, &signed_min)); + OK(uc_reg_write(uc, UC_RISCV_REG_A4, &unsigned_min)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_RISCV_REG_T0, &old_add)); + OK(uc_reg_read(uc, UC_RISCV_REG_T1, &old_swap)); + OK(uc_reg_read(uc, UC_RISCV_REG_T2, &old_signed_min)); + OK(uc_reg_read(uc, UC_RISCV_REG_T3, &old_unsigned_min)); + OK(uc_mem_read(uc, riscv_data_start, &memory, sizeof(memory))); + TEST_CHECK(old_add == 10); + TEST_CHECK(old_swap == 15); + TEST_CHECK(old_signed_min == 20); + TEST_CHECK(old_unsigned_min == signed_min); + TEST_CHECK(memory == unsigned_min); + OK(uc_close(uc)); +} + +static void test_riscv64_amo(void) +{ + uc_engine *uc; + uint32_t insns[] = { + riscv_encode_atomic(0x00, 11, 10, 3, 5), + riscv_encode_atomic(0x01, 12, 10, 3, 6), + riscv_encode_atomic(0x10, 13, 10, 3, 7), + riscv_encode_atomic(0x18, 14, 10, 3, 28), + riscv_encode_atomic(0x00, 15, 16, 2, 29), + }; + uint8_t code[sizeof(insns)]; + uint64_t address = riscv_data_start; + uint64_t word_address = riscv_data_start + sizeof(uint64_t); + uint64_t initial = 10; + uint32_t initial_word = 0x80000005; + uint64_t addend = 5; + uint64_t swap = 20; + uint64_t signed_min = 0xfffffffffffffffdull; + uint64_t unsigned_min = 2; + uint64_t word_addend = 3; + uint64_t old_add = 0; + uint64_t old_swap = 0; + uint64_t old_signed_min = 0; + uint64_t old_unsigned_min = 0; + uint64_t old_word = 0; + uint64_t memory = 0; + uint32_t memory_word = 0; + size_t i; + + for (i = 0; i < sizeof(insns) / sizeof(insns[0]); i++) { + riscv_insn_to_code(&code[i * 4], insns[i]); + } + + uc_common_setup(&uc, UC_ARCH_RISCV, UC_MODE_RISCV64, + (const char *)code, sizeof(code)); + OK(uc_mem_map(uc, riscv_data_start, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, riscv_data_start, &initial, sizeof(initial))); + OK(uc_mem_write(uc, word_address, &initial_word, sizeof(initial_word))); + OK(uc_reg_write(uc, UC_RISCV_REG_A0, &address)); + OK(uc_reg_write(uc, UC_RISCV_REG_A1, &addend)); + OK(uc_reg_write(uc, UC_RISCV_REG_A2, &swap)); + OK(uc_reg_write(uc, UC_RISCV_REG_A3, &signed_min)); + OK(uc_reg_write(uc, UC_RISCV_REG_A4, &unsigned_min)); + OK(uc_reg_write(uc, UC_RISCV_REG_A5, &word_addend)); + OK(uc_reg_write(uc, UC_RISCV_REG_A6, &word_address)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_RISCV_REG_T0, &old_add)); + OK(uc_reg_read(uc, UC_RISCV_REG_T1, &old_swap)); + OK(uc_reg_read(uc, UC_RISCV_REG_T2, &old_signed_min)); + OK(uc_reg_read(uc, UC_RISCV_REG_T3, &old_unsigned_min)); + OK(uc_reg_read(uc, UC_RISCV_REG_T4, &old_word)); + OK(uc_mem_read(uc, riscv_data_start, &memory, sizeof(memory))); + OK(uc_mem_read(uc, word_address, &memory_word, sizeof(memory_word))); + TEST_CHECK(old_add == 10); + TEST_CHECK(old_swap == 15); + TEST_CHECK(old_signed_min == 20); + TEST_CHECK(old_unsigned_min == signed_min); + TEST_CHECK(old_word == 0xffffffff80000005ull); + TEST_CHECK(memory == unsigned_min); + TEST_CHECK(memory_word == 0x80000008); + OK(uc_close(uc)); +} + +static void test_riscv32_atomic_misaligned(void) +{ + uc_engine *uc; + uint32_t insn = riscv_encode_atomic(0x02, 0, 10, 2, 5); + uint8_t code[4]; + uint32_t address = riscv_data_start + 1; + + riscv_insn_to_code(code, insn); + uc_common_setup(&uc, UC_ARCH_RISCV, UC_MODE_RISCV32, + (const char *)code, sizeof(code)); + OK(uc_mem_map(uc, riscv_data_start, 0x1000, UC_PROT_ALL)); + OK(uc_reg_write(uc, UC_RISCV_REG_A0, &address)); + + uc_assert_err(UC_ERR_EXCEPTION, + uc_emu_start(uc, code_start, code_start + sizeof(code), + 0, 0)); + OK(uc_close(uc)); +} + +static void test_riscv64_atomic_misaligned(void) +{ + uc_engine *uc; + uint32_t insn = riscv_encode_atomic(0x00, 11, 10, 3, 5); + uint8_t code[4]; + uint64_t address = riscv_data_start + 4; + uint64_t addend = 1; + uint64_t initial = 0x1122334455667788ull; + uint64_t memory = 0; + + riscv_insn_to_code(code, insn); + uc_common_setup(&uc, UC_ARCH_RISCV, UC_MODE_RISCV64, + (const char *)code, sizeof(code)); + OK(uc_mem_map(uc, riscv_data_start, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address, &initial, sizeof(initial))); + OK(uc_reg_write(uc, UC_RISCV_REG_A0, &address)); + OK(uc_reg_write(uc, UC_RISCV_REG_A1, &addend)); + + uc_assert_err(UC_ERR_EXCEPTION, + uc_emu_start(uc, code_start, code_start + sizeof(code), + 0, 0)); + OK(uc_mem_read(uc, address, &memory, sizeof(memory))); + TEST_CHECK(memory == initial); + OK(uc_close(uc)); +} + static void test_riscv64_sstc_stimecmp(void) { uc_engine *uc; @@ -2099,6 +2421,51 @@ static void test_riscv_rvh_hstatus_layout(void) OK(uc_close(uc)); } +static void test_riscv64_rvh_trap_metadata_csr_access(void) +{ + uc_engine *uc; + uint32_t insns[] = { + riscv_encode_csr(RISCV_CSR_HTVAL, 0, 2, 9), + riscv_encode_csr(RISCV_CSR_HTINST, 0, 2, 10), + riscv_encode_csr(RISCV_CSR_MTVAL2, 5, 1, 0), + riscv_encode_csr(RISCV_CSR_MTINST, 6, 1, 0), + riscv_encode_csr(RISCV_CSR_MTVAL2, 0, 2, 11), + riscv_encode_csr(RISCV_CSR_MTINST, 0, 2, 12), + }; + uint8_t code[sizeof(insns)]; + uint64_t htval = 0x123456789abull; + uint64_t htinst = 0x00002003; + uint64_t mtval2 = 0x23456789abcull; + uint64_t mtinst = 0x00003003; + uint64_t actual[4] = {0}; + size_t i; + + for (i = 0; i < sizeof(insns) / sizeof(insns[0]); i++) { + riscv_insn_to_code(&code[i * 4], insns[i]); + } + + uc_common_setup_model(&uc, UC_ARCH_RISCV, UC_MODE_RISCV64, + (const char *)code, sizeof(code), + UC_CPU_RISCV64_BASE64); + OK(uc_reg_write(uc, UC_RISCV_REG_HTVAL, &htval)); + OK(uc_reg_write(uc, UC_RISCV_REG_HTINST, &htinst)); + OK(uc_reg_write(uc, UC_RISCV_REG_X5, &mtval2)); + OK(uc_reg_write(uc, UC_RISCV_REG_X6, &mtinst)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_RISCV_REG_X9, &actual[0])); + OK(uc_reg_read(uc, UC_RISCV_REG_X10, &actual[1])); + OK(uc_reg_read(uc, UC_RISCV_REG_X11, &actual[2])); + OK(uc_reg_read(uc, UC_RISCV_REG_X12, &actual[3])); + + TEST_CHECK(actual[0] == htval); + TEST_CHECK(actual[1] == htinst); + TEST_CHECK(actual[2] == mtval2); + TEST_CHECK(actual[3] == mtinst); + + OK(uc_close(uc)); +} + static void test_riscv_rvh_hedeleg_mask(void) { uc_engine *uc; @@ -2354,6 +2721,101 @@ static void test_riscv64_type2_exec_privilege_filter(void) TEST_CHECK(value == 1); } +static void test_riscv64_context_check_debug_state(uc_engine *uc, + uint64_t data_address, + uint32_t initial_value) +{ + uint32_t actual = 0; + + uc_assert_err(UC_ERR_EXCEPTION, + uc_emu_start(uc, code_start + 12, code_start + 16, 0, 0)); + uc_assert_err(UC_ERR_EXCEPTION, + uc_emu_start(uc, code_start + 16, code_start + 20, 0, 0)); + OK(uc_mem_read(uc, data_address, &actual, sizeof(actual))); + TEST_CHECK(actual == initial_value); +} + +static void test_riscv64_context_debug_lifecycle(void) +{ + const uint64_t data_address = riscv_data_start; + const uint64_t breakpoint_address = code_start + 12; + const uint64_t exec_control = + RISCV64_TRIGGER_TYPE2 | RISCV_TRIGGER_TYPE2_EXEC | + RISCV_TRIGGER_TYPE2_M; + const uint64_t store_control = + RISCV64_TRIGGER_TYPE2 | RISCV_TRIGGER_TYPE2_STORE | + RISCV_TRIGGER_TYPE2_SIZE_4 | RISCV_TRIGGER_TYPE2_M; + const uint64_t disabled_control = RISCV64_TRIGGER_TYPE2; + const uint64_t stored_value = 0x11223344; + const uint32_t initial_value = 0xaabbccdd; + uint32_t insns[] = { + riscv_encode_csr(RISCV_CSR_TSELECT, 5, 1, 0), + riscv_encode_csr(RISCV_CSR_TDATA2, 6, 1, 0), + riscv_encode_csr(RISCV_CSR_TDATA1, 7, 1, 0), + riscv_encode_addi(10, 0, 1), + riscv_encode_s(0, 9, 6, 2, 0x23), + }; + uint8_t code[sizeof(insns)]; + uc_engine *source; + uc_engine *destination; + uc_context *context; + uint64_t trigger_index; + size_t i; + + for (i = 0; i < sizeof(insns) / sizeof(insns[0]); i++) { + riscv_insn_to_code(&code[i * 4], insns[i]); + } + + uc_common_setup_model(&source, UC_ARCH_RISCV, UC_MODE_RISCV64, + (const char *)code, sizeof(code), + UC_CPU_RISCV64_SIFIVE_U54); + uc_common_setup_model(&destination, UC_ARCH_RISCV, UC_MODE_RISCV64, + (const char *)code, sizeof(code), + UC_CPU_RISCV64_SIFIVE_U54); + OK(uc_mem_map(source, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_map(destination, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(source, data_address, &initial_value, + sizeof(initial_value))); + OK(uc_mem_write(destination, data_address, &initial_value, + sizeof(initial_value))); + + trigger_index = 0; + OK(uc_reg_write(source, UC_RISCV_REG_X5, &trigger_index)); + OK(uc_reg_write(source, UC_RISCV_REG_X6, &breakpoint_address)); + OK(uc_reg_write(source, UC_RISCV_REG_X7, &exec_control)); + OK(uc_emu_start(source, code_start, code_start + 12, 0, 0)); + + trigger_index = 1; + OK(uc_reg_write(source, UC_RISCV_REG_X5, &trigger_index)); + OK(uc_reg_write(source, UC_RISCV_REG_X6, &data_address)); + OK(uc_reg_write(source, UC_RISCV_REG_X7, &store_control)); + OK(uc_reg_write(source, UC_RISCV_REG_X9, &stored_value)); + OK(uc_emu_start(source, code_start, code_start + 12, 0, 0)); + + OK(uc_context_alloc(source, &context)); + OK(uc_context_save(source, context)); + + trigger_index = 0; + OK(uc_reg_write(source, UC_RISCV_REG_X5, &trigger_index)); + OK(uc_reg_write(source, UC_RISCV_REG_X7, &disabled_control)); + OK(uc_emu_start(source, code_start, code_start + 12, 0, 0)); + trigger_index = 1; + OK(uc_reg_write(source, UC_RISCV_REG_X5, &trigger_index)); + OK(uc_emu_start(source, code_start, code_start + 12, 0, 0)); + + OK(uc_context_restore(source, context)); + test_riscv64_context_check_debug_state(source, data_address, + initial_value); + + OK(uc_close(source)); + OK(uc_context_restore(destination, context)); + test_riscv64_context_check_debug_state(destination, data_address, + initial_value); + + OK(uc_context_free(context)); + OK(uc_close(destination)); +} + static uint64_t riscv_pmp_napot_addr(uint64_t base, uint64_t size) { return (base >> 2) | (((size / 2) - 1) >> 2); @@ -2440,6 +2902,336 @@ static void test_riscv64_pmp_na4_rejects_store(void) TEST_CHECK(capture.intno == RISCV_EXCP_STORE_AMO_ACCESS_FAULT); } +static void run_riscv64_pmp_data_access(const uint64_t pmpaddr[4], + uint64_t pmpcfg, uint32_t access_insn, + uint64_t access_addr, + RiscvIntrCapture *capture, + uint64_t *result) +{ + static const uint32_t data[] = { + 0x10203040, + 0x11223344, + 0x22334455, + 0x33445566, + }; + uc_engine *uc; + uc_hook hook; + uint32_t insns[] = { + riscv_encode_csr(RISCV_CSR_PMPADDR0, 5, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPADDR0 + 1, 6, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPADDR0 + 2, 7, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPADDR0 + 3, 10, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPCFG0, 11, 1, 0), + access_insn, + }; + uint8_t code[sizeof(insns)]; + uint64_t priv = 0; + size_t i; + + for (i = 0; i < sizeof(insns) / sizeof(insns[0]); i++) { + riscv_insn_to_code(&code[i * 4], insns[i]); + } + + uc_common_setup_model(&uc, UC_ARCH_RISCV, UC_MODE_RISCV64, + (const char *)code, sizeof(code), + UC_CPU_RISCV64_SIFIVE_U54); + OK(uc_mem_map(uc, riscv_data_start - 0x1000, 0x3000, UC_PROT_ALL)); + OK(uc_mem_write(uc, riscv_data_start, data, sizeof(data))); + OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_riscv_intr_capture_cb, capture, + 1, 0)); + OK(uc_reg_write(uc, UC_RISCV_REG_X5, &pmpaddr[0])); + OK(uc_reg_write(uc, UC_RISCV_REG_X6, &pmpaddr[1])); + OK(uc_reg_write(uc, UC_RISCV_REG_X7, &pmpaddr[2])); + OK(uc_reg_write(uc, UC_RISCV_REG_X10, &pmpaddr[3])); + OK(uc_reg_write(uc, UC_RISCV_REG_X11, &pmpcfg)); + OK(uc_reg_write(uc, UC_RISCV_REG_X8, &access_addr)); + + OK(uc_emu_start(uc, code_start, code_start + 5 * 4, 0, 0)); + OK(uc_reg_write(uc, UC_RISCV_REG_PRIV, &priv)); + OK(uc_emu_start(uc, code_start + 5 * 4, code_start + sizeof(code), 0, 0)); + if (result != NULL) { + OK(uc_reg_read(uc, UC_RISCV_REG_X9, result)); + } + + OK(uc_close(uc)); +} + +static void test_riscv64_pmp_tor_boundaries(void) +{ + const uint32_t load = riscv_encode_i(0, 8, 2, 9, 0x03); + const uint64_t pmpaddr[] = { + riscv_pmp_napot_addr(code_start, 0x1000), + riscv_data_start >> 2, + (riscv_data_start + 16) >> 2, + 0, + }; + const uint64_t pmpcfg = (RISCV_PMPCFG_R | RISCV_PMPCFG_A_TOR) << 16 | + (RISCV_PMPCFG_X | RISCV_PMPCFG_A_NAPOT); + RiscvIntrCapture capture = {0}; + uint64_t value = 0; + + run_riscv64_pmp_data_access(pmpaddr, pmpcfg, load, riscv_data_start, + &capture, &value); + TEST_CHECK(capture.count == 0); + TEST_CHECK(value == 0x10203040); + + capture = (RiscvIntrCapture){0}; + value = 0; + run_riscv64_pmp_data_access(pmpaddr, pmpcfg, load, riscv_data_start + 12, + &capture, &value); + TEST_CHECK(capture.count == 0); + TEST_CHECK(value == 0x33445566); + + capture = (RiscvIntrCapture){0}; + run_riscv64_pmp_data_access(pmpaddr, pmpcfg, load, riscv_data_start - 4, + &capture, NULL); + TEST_CHECK(capture.count == 1); + TEST_CHECK(capture.intno == RISCV_EXCP_LOAD_ACCESS_FAULT); + + capture = (RiscvIntrCapture){0}; + run_riscv64_pmp_data_access(pmpaddr, pmpcfg, load, riscv_data_start + 16, + &capture, NULL); + TEST_CHECK(capture.count == 1); + TEST_CHECK(capture.intno == RISCV_EXCP_LOAD_ACCESS_FAULT); +} + +static void test_riscv64_pmp_napot_boundaries(void) +{ + const uint32_t load = riscv_encode_i(0, 8, 2, 9, 0x03); + const uint64_t pmpaddr[] = { + riscv_pmp_napot_addr(code_start, 0x1000), + riscv_pmp_napot_addr(riscv_data_start, 16), + 0, + 0, + }; + const uint64_t pmpcfg = (RISCV_PMPCFG_R | RISCV_PMPCFG_A_NAPOT) << 8 | + (RISCV_PMPCFG_X | RISCV_PMPCFG_A_NAPOT); + RiscvIntrCapture capture = {0}; + uint64_t value = 0; + + run_riscv64_pmp_data_access(pmpaddr, pmpcfg, load, riscv_data_start, + &capture, &value); + TEST_CHECK(capture.count == 0); + TEST_CHECK(value == 0x10203040); + + capture = (RiscvIntrCapture){0}; + value = 0; + run_riscv64_pmp_data_access(pmpaddr, pmpcfg, load, riscv_data_start + 12, + &capture, &value); + TEST_CHECK(capture.count == 0); + TEST_CHECK(value == 0x33445566); + + capture = (RiscvIntrCapture){0}; + run_riscv64_pmp_data_access(pmpaddr, pmpcfg, load, riscv_data_start - 4, + &capture, NULL); + TEST_CHECK(capture.count == 1); + TEST_CHECK(capture.intno == RISCV_EXCP_LOAD_ACCESS_FAULT); + + capture = (RiscvIntrCapture){0}; + run_riscv64_pmp_data_access(pmpaddr, pmpcfg, load, riscv_data_start + 16, + &capture, NULL); + TEST_CHECK(capture.count == 1); + TEST_CHECK(capture.intno == RISCV_EXCP_LOAD_ACCESS_FAULT); +} + +static void test_riscv64_pmp_entry_priority(void) +{ + const uint32_t load = riscv_encode_i(0, 8, 2, 9, 0x03); + const uint64_t pmpaddr[] = { + riscv_pmp_napot_addr(code_start, 0x1000), + riscv_pmp_napot_addr(riscv_data_start, 0x1000), + riscv_pmp_napot_addr(riscv_data_start, 16), + 0, + }; + uint64_t pmpcfg = (RISCV_PMPCFG_R | RISCV_PMPCFG_A_NAPOT) << 16 | + RISCV_PMPCFG_A_NAPOT << 8 | + (RISCV_PMPCFG_X | RISCV_PMPCFG_A_NAPOT); + RiscvIntrCapture capture = {0}; + uint64_t value = 0; + + run_riscv64_pmp_data_access(pmpaddr, pmpcfg, load, riscv_data_start, + &capture, NULL); + TEST_CHECK(capture.count == 1); + TEST_CHECK(capture.intno == RISCV_EXCP_LOAD_ACCESS_FAULT); + + pmpcfg = RISCV_PMPCFG_A_NAPOT << 16 | + (RISCV_PMPCFG_R | RISCV_PMPCFG_A_NAPOT) << 8 | + (RISCV_PMPCFG_X | RISCV_PMPCFG_A_NAPOT); + capture = (RiscvIntrCapture){0}; + run_riscv64_pmp_data_access(pmpaddr, pmpcfg, load, riscv_data_start, + &capture, &value); + TEST_CHECK(capture.count == 0); + TEST_CHECK(value == 0x10203040); +} + +static void test_riscv64_pmp_locked_entry(void) +{ + uc_engine *uc; + uint32_t insns[] = { + riscv_encode_csr(RISCV_CSR_PMPADDR0 + 1, 5, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPCFG0, 6, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPADDR0 + 1, 7, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPCFG0, 8, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPADDR0 + 1, 0, 2, 9), + riscv_encode_csr(RISCV_CSR_PMPCFG0, 0, 2, 10), + }; + uint8_t code[sizeof(insns)]; + uint64_t initial_addr = riscv_pmp_napot_addr(riscv_data_start, 16); + uint64_t attempted_addr = + riscv_pmp_napot_addr(riscv_data_start + 0x100, 16); + uint64_t locked_cfg = + (RISCV_PMPCFG_R | RISCV_PMPCFG_A_NAPOT | RISCV_PMPCFG_L) << 8; + uint64_t attempted_cfg = (RISCV_PMPCFG_W | RISCV_PMPCFG_A_NAPOT) << 8; + uint64_t observed_addr = 0; + uint64_t observed_cfg = 0; + size_t i; + + for (i = 0; i < sizeof(insns) / sizeof(insns[0]); i++) { + riscv_insn_to_code(&code[i * 4], insns[i]); + } + uc_common_setup_model(&uc, UC_ARCH_RISCV, UC_MODE_RISCV64, + (const char *)code, sizeof(code), + UC_CPU_RISCV64_SIFIVE_U54); + OK(uc_reg_write(uc, UC_RISCV_REG_X5, &initial_addr)); + OK(uc_reg_write(uc, UC_RISCV_REG_X6, &locked_cfg)); + OK(uc_reg_write(uc, UC_RISCV_REG_X7, &attempted_addr)); + OK(uc_reg_write(uc, UC_RISCV_REG_X8, &attempted_cfg)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_RISCV_REG_X9, &observed_addr)); + OK(uc_reg_read(uc, UC_RISCV_REG_X10, &observed_cfg)); + TEST_CHECK_(observed_addr == initial_addr, "locked pmpaddr1 = 0x%llx", + (unsigned long long)observed_addr); + TEST_CHECK_(observed_cfg == locked_cfg, "locked pmpcfg0 = 0x%llx", + (unsigned long long)observed_cfg); + + OK(uc_close(uc)); +} + +static void test_riscv64_pmp_locked_tor_lower_bound(void) +{ + uc_engine *uc; + uint32_t insns[] = { + riscv_encode_csr(RISCV_CSR_PMPADDR0 + 1, 5, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPADDR0 + 2, 6, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPCFG0, 7, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPADDR0 + 1, 8, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPADDR0 + 1, 0, 2, 9), + }; + uint8_t code[sizeof(insns)]; + uint64_t lower = riscv_data_start >> 2; + uint64_t upper = (riscv_data_start + 16) >> 2; + uint64_t attempted_lower = (riscv_data_start + 4) >> 2; + uint64_t locked_tor_cfg = + (RISCV_PMPCFG_R | RISCV_PMPCFG_A_TOR | RISCV_PMPCFG_L) << 16; + uint64_t observed_lower = 0; + size_t i; + + for (i = 0; i < sizeof(insns) / sizeof(insns[0]); i++) { + riscv_insn_to_code(&code[i * 4], insns[i]); + } + uc_common_setup_model(&uc, UC_ARCH_RISCV, UC_MODE_RISCV64, + (const char *)code, sizeof(code), + UC_CPU_RISCV64_SIFIVE_U54); + OK(uc_reg_write(uc, UC_RISCV_REG_X5, &lower)); + OK(uc_reg_write(uc, UC_RISCV_REG_X6, &upper)); + OK(uc_reg_write(uc, UC_RISCV_REG_X7, &locked_tor_cfg)); + OK(uc_reg_write(uc, UC_RISCV_REG_X8, &attempted_lower)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_RISCV_REG_X9, &observed_lower)); + TEST_CHECK_(observed_lower == lower, "locked TOR lower bound = 0x%llx", + (unsigned long long)observed_lower); + + OK(uc_close(uc)); +} + +static void run_riscv64_pmp_execute(bool executable, RiscvIntrCapture *capture, + uint64_t *result) +{ + const uint64_t target_address = code_start + 0x1000; + uc_engine *uc; + uc_hook hook; + uint32_t insns[] = { + riscv_encode_csr(RISCV_CSR_PMPADDR0, 5, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPADDR0 + 1, 6, 1, 0), + riscv_encode_csr(RISCV_CSR_PMPCFG0, 7, 1, 0), + }; + uint32_t target_insn = riscv_encode_i(1, 0, 0, 9, 0x13); + uint8_t code[sizeof(insns)]; + uint8_t target_code[4]; + uint64_t setup_addr = riscv_pmp_napot_addr(code_start, 0x1000); + uint64_t target_addr = riscv_pmp_napot_addr(target_address, 0x1000); + uint64_t target_cfg = RISCV_PMPCFG_R | RISCV_PMPCFG_A_NAPOT; + uint64_t pmpcfg; + uint64_t priv = 0; + size_t i; + + if (executable) { + target_cfg |= RISCV_PMPCFG_X; + } + pmpcfg = target_cfg << 8 | (RISCV_PMPCFG_X | RISCV_PMPCFG_A_NAPOT); + for (i = 0; i < sizeof(insns) / sizeof(insns[0]); i++) { + riscv_insn_to_code(&code[i * 4], insns[i]); + } + riscv_insn_to_code(target_code, target_insn); + + uc_common_setup_model(&uc, UC_ARCH_RISCV, UC_MODE_RISCV64, + (const char *)code, sizeof(code), + UC_CPU_RISCV64_SIFIVE_U54); + OK(uc_mem_write(uc, target_address, target_code, sizeof(target_code))); + OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_riscv_intr_capture_cb, capture, + 1, 0)); + OK(uc_reg_write(uc, UC_RISCV_REG_X5, &setup_addr)); + OK(uc_reg_write(uc, UC_RISCV_REG_X6, &target_addr)); + OK(uc_reg_write(uc, UC_RISCV_REG_X7, &pmpcfg)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_write(uc, UC_RISCV_REG_PRIV, &priv)); + OK(uc_emu_start(uc, target_address, target_address + sizeof(target_code), 0, + 0)); + OK(uc_reg_read(uc, UC_RISCV_REG_X9, result)); + + OK(uc_close(uc)); +} + +static void test_riscv64_pmp_execute_permission(void) +{ + RiscvIntrCapture capture = {0}; + uint64_t result = 0; + + run_riscv64_pmp_execute(true, &capture, &result); + TEST_CHECK(capture.count == 0); + TEST_CHECK(result == 1); + + capture = (RiscvIntrCapture){0}; + result = 0; + run_riscv64_pmp_execute(false, &capture, &result); + TEST_CHECK(capture.count == 1); + TEST_CHECK(capture.intno == RISCV_EXCP_INST_ACCESS_FAULT); + TEST_CHECK(result == 0); +} + +static void test_riscv64_pmp_access_crosses_two_regions(void) +{ + const uint32_t load = riscv_encode_i(0, 8, 3, 9, 0x03); + const uint64_t pmpaddr[] = { + riscv_pmp_napot_addr(code_start, 0x1000), + riscv_data_start >> 2, + (riscv_data_start + 4) >> 2, + (riscv_data_start + 8) >> 2, + }; + const uint64_t pmpcfg = (RISCV_PMPCFG_R | RISCV_PMPCFG_A_TOR) << 24 | + (RISCV_PMPCFG_R | RISCV_PMPCFG_A_TOR) << 16 | + (RISCV_PMPCFG_X | RISCV_PMPCFG_A_NAPOT); + RiscvIntrCapture capture = {0}; + + run_riscv64_pmp_data_access(pmpaddr, pmpcfg, load, riscv_data_start, + &capture, NULL); + TEST_CHECK(capture.count == 1); + TEST_CHECK(capture.intno == RISCV_EXCP_LOAD_ACCESS_FAULT); +} + static void run_riscv64_rvh_virtual_instruction(uint32_t insn) { uc_engine *uc; @@ -9636,7 +10428,6 @@ static void test_riscv64_rvv_mask_set(void) static void test_riscv64_rvv_viota_vid(void) { uc_engine *uc; - uint8_t code[8 * 4]; uint32_t insns[] = { riscv_encode_rvv_vsetvli(0, 16, 0xc8), riscv_encode_rvv_mask_ldst(0, 10, 0), @@ -10573,7 +11364,6 @@ static void test_riscv64_rvv_float_reduction_illegal(void) static void test_riscv64_rvv_divide_remainder(void) { uc_engine *uc; - uint8_t code[20 * 4]; uint32_t insns[] = { riscv_encode_rvv_vsetvli(0, 5, 0xc8), riscv_encode_rvv_mask_ldst(0, 10, 0), @@ -11397,6 +12187,14 @@ static void test_riscv64_rvv_requires_vs(void) TEST_LIST = { {"test_riscv32_nop", test_riscv32_nop}, {"test_riscv64_nop", test_riscv64_nop}, + {"test_riscv64_fetch_invalid_compressed_size", + test_riscv64_fetch_invalid_compressed_size}, + {"test_riscv32_lr_sc", test_riscv32_lr_sc}, + {"test_riscv64_lr_sc", test_riscv64_lr_sc}, + {"test_riscv32_amo", test_riscv32_amo}, + {"test_riscv64_amo", test_riscv64_amo}, + {"test_riscv32_atomic_misaligned", test_riscv32_atomic_misaligned}, + {"test_riscv64_atomic_misaligned", test_riscv64_atomic_misaligned}, {"test_riscv64_sstc_stimecmp", test_riscv64_sstc_stimecmp}, {"test_riscv32_zihintpause", test_riscv32_zihintpause}, {"test_riscv64_zihintpause", test_riscv64_zihintpause}, @@ -11441,6 +12239,8 @@ TEST_LIST = { test_riscv64_type2_store_privilege_filter}, {"test_riscv64_type2_exec_trigger", test_riscv64_type2_exec_trigger}, + {"test_riscv64_context_debug_lifecycle", + test_riscv64_context_debug_lifecycle}, {"test_riscv64_type2_exec_privilege_filter", test_riscv64_type2_exec_privilege_filter}, {"test_riscv64_pmp_na4_load", test_riscv64_pmp_na4_load}, @@ -11448,6 +12248,16 @@ TEST_LIST = { test_riscv64_pmp_na4_rejects_outside}, {"test_riscv64_pmp_na4_rejects_store", test_riscv64_pmp_na4_rejects_store}, + {"test_riscv64_pmp_tor_boundaries", test_riscv64_pmp_tor_boundaries}, + {"test_riscv64_pmp_napot_boundaries", test_riscv64_pmp_napot_boundaries}, + {"test_riscv64_pmp_entry_priority", test_riscv64_pmp_entry_priority}, + {"test_riscv64_pmp_locked_entry", test_riscv64_pmp_locked_entry}, + {"test_riscv64_pmp_locked_tor_lower_bound", + test_riscv64_pmp_locked_tor_lower_bound}, + {"test_riscv64_pmp_execute_permission", + test_riscv64_pmp_execute_permission}, + {"test_riscv64_pmp_access_crosses_two_regions", + test_riscv64_pmp_access_crosses_two_regions}, {"test_riscv32_svinval", test_riscv32_svinval}, {"test_riscv64_svinval", test_riscv64_svinval}, {"test_riscv_svinval_hinval_requires_rvh", @@ -11460,6 +12270,8 @@ TEST_LIST = { {"test_riscv_rvh_requires_h", test_riscv_rvh_requires_h}, {"test_riscv_rvh_requires_hlsx", test_riscv_rvh_requires_hlsx}, {"test_riscv_rvh_hstatus_layout", test_riscv_rvh_hstatus_layout}, + {"test_riscv64_rvh_trap_metadata_csr_access", + test_riscv64_rvh_trap_metadata_csr_access}, {"test_riscv_rvh_hedeleg_mask", test_riscv_rvh_hedeleg_mask}, {"test_riscv_rvh_hu_allows_u_mode", test_riscv_rvh_hu_allows_u_mode}, {"test_riscv_rvh_hu_tb_flags", test_riscv_rvh_hu_tb_flags}, diff --git a/tests/unit/test_s390x.c b/tests/unit/test_s390x.c index f6bed82b2c..6b5c6a60e3 100644 --- a/tests/unit/test_s390x.c +++ b/tests/unit/test_s390x.c @@ -176,6 +176,25 @@ static uint32_t s390x_read_cc(uc_engine *uc) return (pswm >> 44) & 3; } +typedef struct S390xExrlTrace { + uint64_t code_address[2]; + uint32_t code_size[2]; + uint32_t code_count; +} S390xExrlTrace; + +static void s390x_exrl_code_hook(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + S390xExrlTrace *trace = (S390xExrlTrace *)user_data; + + (void)uc; + if (trace->code_count < 2) { + trace->code_address[trace->code_count] = address; + trace->code_size[trace->code_count] = size; + } + trace->code_count++; +} + static void run_s390x_chrl_case(const uint8_t code[6], const uint8_t data[8], uint64_t r4, uint32_t expected_cc) @@ -251,6 +270,253 @@ static void s390x_setup_scalar_case(uc_engine **uc, const uint8_t *code, OK(uc_mem_write(*uc, code_start, code, code_size)); } +static void test_s390x_instruction_count_pc_boundary(void) +{ + static const size_t counts[] = { + 1, 2, 3, + 32766, 32767, 32768, 32769, + 65534, 65535, 65536, 65537, + 70000, + }; + const uint8_t code[] = { + 0xa7, 0x3b, 0x00, 0x01, + 0xa7, 0xf4, 0xff, 0xfe, + }; + uc_engine *uc; + uint64_t r3; + uint64_t pc; + size_t i; + + s390x_setup_scalar_case(&uc, code, sizeof(code)); + + for (i = 0; i < sizeof(counts) / sizeof(counts[0]); i++) { + uint64_t expected = (counts[i] + 1) / 2; + + r3 = 0; + OK(uc_reg_write(uc, UC_S390X_REG_R3, &r3)); + OK(uc_emu_start(uc, code_start, 0, 0, counts[i])); + OK(uc_reg_read(uc, UC_S390X_REG_R3, &r3)); + OK(uc_reg_read(uc, UC_S390X_REG_PC, &pc)); + TEST_CHECK_(r3 == expected, "count=%zu r3=%llu", + counts[i], (unsigned long long)r3); + TEST_CHECK_(pc == code_start + (counts[i] & 1) * 4, + "count=%zu pc=0x%llx", counts[i], + (unsigned long long)pc); + } + + OK(uc_close(uc)); +} + +static void test_s390x_compare_and_swap_32(void) +{ + const uint8_t code[] = { 0xba, 0x45, 0x20, 0x00 }; + const uint8_t initial[] = { 0x11, 0x22, 0x33, 0x44 }; + const uint8_t replacement[] = { 0xaa, 0xbb, 0xcc, 0xdd }; + const uint8_t mismatch[] = { 0x10, 0x20, 0x30, 0x40 }; + uint8_t actual[sizeof(initial)]; + uc_engine *uc; + uint64_t r2 = s390x_data_start; + uint64_t r4 = 0xdeadbeef11223344ull; + uint64_t r5 = 0x01234567aabbccddull; + + s390x_setup_scalar_case(&uc, code, sizeof(code)); + OK(uc_reg_write(uc, UC_S390X_REG_R2, &r2)); + OK(uc_mem_write(uc, s390x_data_start, initial, sizeof(initial))); + OK(uc_reg_write(uc, UC_S390X_REG_R4, &r4)); + OK(uc_reg_write(uc, UC_S390X_REG_R5, &r5)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_S390X_REG_R4, &r4)); + OK(uc_mem_read(uc, s390x_data_start, actual, sizeof(actual))); + TEST_CHECK(r4 == 0xdeadbeef11223344ull); + TEST_CHECK(s390x_read_cc(uc) == 0); + s390x_check_bytes("cs success", actual, replacement, sizeof(actual)); + + r4 = 0xdeadbeef11223344ull; + OK(uc_mem_write(uc, s390x_data_start, mismatch, sizeof(mismatch))); + OK(uc_reg_write(uc, UC_S390X_REG_R4, &r4)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_S390X_REG_R4, &r4)); + OK(uc_mem_read(uc, s390x_data_start, actual, sizeof(actual))); + TEST_CHECK(r4 == 0xdeadbeef10203040ull); + TEST_CHECK(s390x_read_cc(uc) == 1); + s390x_check_bytes("cs failure", actual, mismatch, sizeof(actual)); + + OK(uc_close(uc)); +} + +static void test_s390x_compare_and_swap_64(void) +{ + const uint8_t code[] = { 0xeb, 0x45, 0x20, 0x00, 0x00, 0x30 }; + const uint8_t initial[] = { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + }; + const uint8_t replacement[] = { + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, + }; + const uint8_t mismatch[] = { + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, + }; + uint8_t actual[sizeof(initial)]; + uc_engine *uc; + uint64_t r2 = s390x_data_start; + uint64_t r4 = 0x1122334455667788ull; + uint64_t r5 = 0xaabbccddeeff0011ull; + + s390x_setup_scalar_case(&uc, code, sizeof(code)); + OK(uc_reg_write(uc, UC_S390X_REG_R2, &r2)); + OK(uc_mem_write(uc, s390x_data_start, initial, sizeof(initial))); + OK(uc_reg_write(uc, UC_S390X_REG_R4, &r4)); + OK(uc_reg_write(uc, UC_S390X_REG_R5, &r5)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_S390X_REG_R4, &r4)); + OK(uc_mem_read(uc, s390x_data_start, actual, sizeof(actual))); + TEST_CHECK(r4 == 0x1122334455667788ull); + TEST_CHECK(s390x_read_cc(uc) == 0); + s390x_check_bytes("csg success", actual, replacement, sizeof(actual)); + + r4 = 0x1122334455667788ull; + OK(uc_mem_write(uc, s390x_data_start, mismatch, sizeof(mismatch))); + OK(uc_reg_write(uc, UC_S390X_REG_R4, &r4)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_S390X_REG_R4, &r4)); + OK(uc_mem_read(uc, s390x_data_start, actual, sizeof(actual))); + TEST_CHECK(r4 == 0x1020304050607080ull); + TEST_CHECK(s390x_read_cc(uc) == 1); + s390x_check_bytes("csg failure", actual, mismatch, sizeof(actual)); + + OK(uc_close(uc)); +} + +static void test_s390x_laalg(void) +{ + const uint8_t code[] = { 0xeb, 0x43, 0x20, 0x00, 0x00, 0xea }; + const uint8_t initial[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, + }; + const uint8_t expected[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, + }; + const uint8_t maximum[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }; + const uint8_t zero[8] = { 0 }; + uint8_t actual[sizeof(initial)]; + uc_engine *uc; + uint64_t r2 = s390x_data_start; + uint64_t r3 = 2; + uint64_t r4 = 0; + + s390x_setup_scalar_case(&uc, code, sizeof(code)); + OK(uc_reg_write(uc, UC_S390X_REG_R2, &r2)); + OK(uc_reg_write(uc, UC_S390X_REG_R3, &r3)); + OK(uc_mem_write(uc, s390x_data_start, initial, sizeof(initial))); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_S390X_REG_R4, &r4)); + OK(uc_mem_read(uc, s390x_data_start, actual, sizeof(actual))); + TEST_CHECK(r4 == 40); + TEST_CHECK(s390x_read_cc(uc) == 1); + s390x_check_bytes("laalg no carry", actual, expected, sizeof(actual)); + + r3 = 1; + OK(uc_reg_write(uc, UC_S390X_REG_R3, &r3)); + OK(uc_mem_write(uc, s390x_data_start, maximum, sizeof(maximum))); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_S390X_REG_R4, &r4)); + OK(uc_mem_read(uc, s390x_data_start, actual, sizeof(actual))); + TEST_CHECK(r4 == UINT64_MAX); + TEST_CHECK(s390x_read_cc(uc) == 2); + s390x_check_bytes("laalg carry", actual, zero, sizeof(actual)); + + OK(uc_close(uc)); +} + +static void test_s390x_exrl_trt(void) +{ + const uint8_t code[] = { + 0xdd, 0x00, 0x30, 0x00, 0x40, 0x00, + 0xc6, 0x50, 0xff, 0xff, 0xff, 0xfd, + }; + const uint8_t operand[] = { 'h', 'e', 'l', 'l', 'o', 0 }; + uint8_t table[256] = { 0 }; + uint8_t actual_target[6]; + S390xExrlTrace trace = { 0 }; + uc_engine *uc; + uc_hook code_hook; + uint64_t r1 = UINT64_MAX; + uint64_t r2 = UINT64_MAX; + uint64_t r3 = s390x_data_start; + uint64_t r4 = s390x_data_start + 0x100; + uint64_t r5 = 5; + uint64_t pc; + uint64_t pswm; + + table[0] = 0xaa; + s390x_setup_scalar_case(&uc, code, sizeof(code)); + OK(uc_mem_write(uc, r3, operand, sizeof(operand))); + OK(uc_mem_write(uc, r4, table, sizeof(table))); + OK(uc_reg_read(uc, UC_S390X_REG_PSWM, &pswm)); + pswm |= 1ull << 32; + OK(uc_reg_write(uc, UC_S390X_REG_PSWM, &pswm)); + OK(uc_reg_write(uc, UC_S390X_REG_R1, &r1)); + OK(uc_reg_write(uc, UC_S390X_REG_R2, &r2)); + OK(uc_reg_write(uc, UC_S390X_REG_R3, &r3)); + OK(uc_reg_write(uc, UC_S390X_REG_R4, &r4)); + OK(uc_reg_write(uc, UC_S390X_REG_R5, &r5)); + OK(uc_hook_add(uc, &code_hook, UC_HOOK_CODE, + s390x_exrl_code_hook, &trace, 1, 0)); + + OK(uc_emu_start(uc, code_start + 6, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_S390X_REG_R1, &r1)); + OK(uc_reg_read(uc, UC_S390X_REG_R2, &r2)); + OK(uc_reg_read(uc, UC_S390X_REG_PC, &pc)); + OK(uc_mem_read(uc, code_start, actual_target, sizeof(actual_target))); + TEST_CHECK_(r1 == r3 + 5, "r1=0x%llx expected=0x%llx", + (unsigned long long)r1, + (unsigned long long)(r3 + 5)); + TEST_CHECK(r2 == 0xffffffffffffffaaull); + TEST_CHECK(s390x_read_cc(uc) == 2); + TEST_CHECK(pc == code_start + sizeof(code)); + TEST_CHECK(trace.code_count == 1); + TEST_CHECK(trace.code_address[0] == code_start + 6); + TEST_CHECK(trace.code_size[0] == 6); + s390x_check_bytes("exrl target", actual_target, code, + sizeof(actual_target)); + + memset(&trace, 0, sizeof(trace)); + r1 = UINT64_MAX; + r2 = UINT64_MAX; + r4 = 0x6000; + OK(uc_reg_write(uc, UC_S390X_REG_R1, &r1)); + OK(uc_reg_write(uc, UC_S390X_REG_R2, &r2)); + OK(uc_reg_write(uc, UC_S390X_REG_R4, &r4)); + + uc_assert_err(UC_ERR_EXCEPTION, + uc_emu_start(uc, code_start + 6, + code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_S390X_REG_R1, &r1)); + OK(uc_reg_read(uc, UC_S390X_REG_R2, &r2)); + OK(uc_reg_read(uc, UC_S390X_REG_PC, &pc)); + TEST_CHECK(r1 == UINT64_MAX); + TEST_CHECK(r2 == UINT64_MAX); + TEST_CHECK(pc == code_start + 6); + TEST_CHECK(trace.code_count == 1); + TEST_CHECK(trace.code_address[0] == code_start + 6); + TEST_CHECK(trace.code_size[0] == 6); + + OK(uc_close(uc)); +} + static void test_s390x_mie2_add_sub_halfword(void) { const uint8_t code[] = { @@ -1515,6 +1781,14 @@ static void test_s390x_ve2_double_shift(void) TEST_LIST = { {"test_s390x_lr", test_s390x_lr}, + {"test_s390x_instruction_count_pc_boundary", + test_s390x_instruction_count_pc_boundary}, + {"test_s390x_compare_and_swap_32", + test_s390x_compare_and_swap_32}, + {"test_s390x_compare_and_swap_64", + test_s390x_compare_and_swap_64}, + {"test_s390x_laalg", test_s390x_laalg}, + {"test_s390x_exrl_trt", test_s390x_exrl_trt}, {"test_s390x_compare_halfword_relative_long", test_s390x_compare_halfword_relative_long}, {"test_s390x_compare_logical_immediate_trap", diff --git a/tests/unit/test_sparc.c b/tests/unit/test_sparc.c index 602a5a1e9d..d96f53465a 100644 --- a/tests/unit/test_sparc.c +++ b/tests/unit/test_sparc.c @@ -2,16 +2,72 @@ const uint64_t code_start = 0x1000; const uint64_t code_len = 0x4000; +const uint64_t sparc64_code_start = 0x10000; +const uint64_t data_start = 0x8000; +const uint64_t data_len = 0x2000; + +static uint32_t load_be32(const uint8_t *data) +{ + return ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) | + ((uint32_t)data[2] << 8) | data[3]; +} + +static uint64_t load_be64(const uint8_t *data) +{ + return ((uint64_t)load_be32(data) << 32) | load_be32(data + 4); +} + +static void store_be32(uint8_t *data, uint32_t value) +{ + data[0] = value >> 24; + data[1] = value >> 16; + data[2] = value >> 8; + data[3] = value; +} + +static void sparc64_setup(uc_engine **uc, const uint8_t *code, size_t size) +{ + OK(uc_open(UC_ARCH_SPARC, UC_MODE_SPARC64 | UC_MODE_BIG_ENDIAN, uc)); + OK(uc_ctl_set_cpu_model(*uc, UC_CPU_SPARC64_SUN_ULTRASPARC_IV)); + OK(uc_mem_map(*uc, sparc64_code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(*uc, sparc64_code_start, code, size)); +} + +static void test_sparc64_engine_lifecycle(void) +{ + size_t i; + + for (i = 0; i < 16; i++) { + uc_engine *uc; + + OK(uc_open(UC_ARCH_SPARC, UC_MODE_SPARC64 | UC_MODE_BIG_ENDIAN, + &uc)); + OK(uc_close(uc)); + } +} + +static void test_sparc64_bpr_reserved_condition(void) +{ + static const uint8_t code[] = { 0x00, 0xf0, 0x20, 0xe3 }; + uc_engine *uc; + + sparc64_setup(&uc, code, sizeof(code)); + TEST_CHECK(uc_emu_start(uc, sparc64_code_start, + sparc64_code_start + sizeof(code), 0, 0) == + UC_ERR_INSN_INVALID); + OK(uc_close(uc)); +} static void test_virtual_read(void) { uc_engine *uc; uint8_t u8 = 8; - OK(uc_open(UC_ARCH_SPARC, UC_MODE_SPARC32|UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_open(UC_ARCH_SPARC, UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN, &uc)); OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); - uc_assert_err(UC_ERR_ARG, uc_vmem_read(uc, code_start, UC_PROT_READ, &u8, sizeof(u8))); + uc_assert_err(UC_ERR_ARG, + uc_vmem_read(uc, code_start, UC_PROT_READ, &u8, sizeof(u8))); OK(uc_ctl_tlb_mode(uc, UC_TLB_VIRTUAL)); OK(uc_vmem_read(uc, code_start, UC_PROT_READ, &u8, sizeof(u8))); @@ -119,8 +175,7 @@ typedef struct SparcIntrCapture { uint32_t intno; } SparcIntrCapture; -static void test_sparc32_unaligned_cb(uc_engine *uc, uint32_t intno, - void *data) +static void test_sparc32_unaligned_cb(uc_engine *uc, uint32_t intno, void *data) { SparcIntrCapture *capture = (SparcIntrCapture *)data; uint32_t mmu_fault_address = 0x1400; @@ -134,7 +189,7 @@ static void test_sparc32_unaligned_access_sets_fault_address(void) { uc_engine *uc; uc_hook hook; - SparcIntrCapture capture = { 0 }; + SparcIntrCapture capture = {0}; char code[] = ("\xc1\x18\x60\x00" /* ldd [g1],f0 */ "\xc4\x80\x40\x80" /* lda [g1] 4,g2 */); uint32_t address = 0x8001; @@ -145,8 +200,8 @@ static void test_sparc32_unaligned_access_sets_fault_address(void) OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); OK(uc_mem_write(uc, code_start, code, sizeof(code) - 1)); OK(uc_mem_map(uc, 0x8000, 0x1000, UC_PROT_ALL)); - OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_sparc32_unaligned_cb, - &capture, 1, 0)); + OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_sparc32_unaligned_cb, &capture, + 1, 0)); OK(uc_reg_read(uc, UC_SPARC_REG_PSR, &psr)); psr |= (1 << 12) | (1 << 7); OK(uc_reg_write(uc, UC_SPARC_REG_PSR, &psr)); @@ -165,11 +220,560 @@ static void test_sparc32_unaligned_access_sets_fault_address(void) OK(uc_close(uc)); } +static void test_sparc32_delay_slot_count(void) +{ + const uint8_t code[] = { + 0x10, 0x80, 0x00, 0x03, /* ba code_start + 0xc */ + 0x82, 0x10, 0x20, 0x01, /* mov 1, %g1 */ + 0x82, 0x10, 0x20, 0x02, /* mov 2, %g1 */ + 0x84, 0x10, 0x20, 0x03, /* mov 3, %g2 */ + }; + uc_engine *uc; + uint32_t g1 = 0; + uint32_t g2 = 0; + uint32_t pc = 0; + + OK(uc_open(UC_ARCH_SPARC, UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 1)); + OK(uc_reg_read(uc, UC_SPARC_REG_G1, &g1)); + OK(uc_reg_read(uc, UC_SPARC_REG_G2, &g2)); + OK(uc_reg_read(uc, UC_SPARC_REG_PC, &pc)); + TEST_CHECK(g1 == 0); + TEST_CHECK(g2 == 0); + TEST_CHECK(pc == (uint32_t)(code_start + 4)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 2)); + OK(uc_reg_read(uc, UC_SPARC_REG_G1, &g1)); + OK(uc_reg_read(uc, UC_SPARC_REG_G2, &g2)); + OK(uc_reg_read(uc, UC_SPARC_REG_PC, &pc)); + TEST_CHECK(g1 == 1); + TEST_CHECK(g2 == 0); + TEST_CHECK(pc == (uint32_t)(code_start + 12)); + + OK(uc_close(uc)); +} + +typedef struct SparcDelaySlotStop { + uc_err error; + uint32_t calls; +} SparcDelaySlotStop; + +static void sparc_delay_slot_stop_cb(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + SparcDelaySlotStop *stop = (SparcDelaySlotStop *)user_data; + + stop->calls++; + stop->error = uc_emu_stop(uc); +} + +static void test_sparc32_delay_slot_pending_stop(void) +{ + const uint8_t code[] = { + 0x10, 0x80, 0x00, 0x02, /* ba code_start + 8 */ + 0x82, 0x10, 0x20, 0x01, /* mov 1, %g1 */ + 0x84, 0x10, 0x20, 0x02, /* mov 2, %g2 */ + }; + SparcDelaySlotStop stop = {0}; + uint32_t g1 = 0; + uint32_t g2 = 0; + uint32_t pc; + uc_engine *uc; + uc_hook hook; + + OK(uc_open(UC_ARCH_SPARC, UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, sparc_delay_slot_stop_cb, + &stop, code_start + 4, code_start + 4)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(stop.error); + OK(uc_reg_read(uc, UC_SPARC_REG_G1, &g1)); + OK(uc_reg_read(uc, UC_SPARC_REG_G2, &g2)); + OK(uc_reg_read(uc, UC_SPARC_REG_PC, &pc)); + TEST_CHECK(stop.calls == 1); + TEST_CHECK(g1 == 0); + TEST_CHECK(g2 == 0); + TEST_CHECK_(pc == code_start + 4, "pc=0x%x", pc); + + OK(uc_close(uc)); +} + +static void test_sparc32_branch_always_annul(void) +{ + const uint8_t code[] = { + 0x30, 0x80, 0x00, 0x03, /* ba,a code_start + 0xc */ + 0x82, 0x10, 0x20, 0x01, /* mov 1, %g1 */ + 0x82, 0x10, 0x20, 0x02, /* mov 2, %g1 */ + 0x84, 0x10, 0x20, 0x03, /* mov 3, %g2 */ + }; + uc_engine *uc; + uint32_t g1 = 0; + uint32_t g2 = 0; + uint32_t pc = 0; + + OK(uc_open(UC_ARCH_SPARC, UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 1)); + OK(uc_reg_read(uc, UC_SPARC_REG_G1, &g1)); + OK(uc_reg_read(uc, UC_SPARC_REG_G2, &g2)); + OK(uc_reg_read(uc, UC_SPARC_REG_PC, &pc)); + TEST_CHECK(g1 == 0); + TEST_CHECK(g2 == 0); + TEST_CHECK(pc == (uint32_t)(code_start + 12)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 2)); + OK(uc_reg_read(uc, UC_SPARC_REG_G1, &g1)); + OK(uc_reg_read(uc, UC_SPARC_REG_G2, &g2)); + OK(uc_reg_read(uc, UC_SPARC_REG_PC, &pc)); + TEST_CHECK(g1 == 0); + TEST_CHECK(g2 == 3); + TEST_CHECK(pc == (uint32_t)(code_start + sizeof(code))); + + OK(uc_close(uc)); +} + +static void test_sparc32_context_roundtrip(void) +{ + uc_engine *uc; + uc_context *context; + uint32_t g1 = 0x11223344; + uint32_t pc = code_start + 0x20; + uint32_t changed = 0; + + OK(uc_open(UC_ARCH_SPARC, UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_reg_write(uc, UC_SPARC_REG_G1, &g1)); + OK(uc_reg_write(uc, UC_SPARC_REG_PC, &pc)); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + OK(uc_reg_write(uc, UC_SPARC_REG_G1, &changed)); + OK(uc_reg_write(uc, UC_SPARC_REG_PC, &changed)); + OK(uc_context_restore(uc, context)); + + OK(uc_reg_read(uc, UC_SPARC_REG_G1, &changed)); + TEST_CHECK(changed == g1); + OK(uc_reg_read(uc, UC_SPARC_REG_PC, &changed)); + TEST_CHECK(changed == pc); + + OK(uc_context_free(context)); + OK(uc_close(uc)); +} + +static void test_sparc64_vis1_vis2(void) +{ + const uint8_t code[] = { + 0x8d, 0x80, 0x20, 0x04, /* wr %g0,4,%fprs */ + 0x89, 0xb0, 0x09, 0x62, /* fpmerge %f0,%f2,%f4 */ + 0xa7, 0x80, 0x40, 0x00, /* wr %g1,%g0,%gsr */ + 0x8d, 0xb0, 0x09, 0x82, /* bshuffle %f0,%f2,%f6 */ + }; + uc_engine *uc; + uint64_t gsr = UINT64_C(0x08192a3b) << 32; + uint32_t f0 = 0x00112233; + uint32_t f1 = 0x44556677; + uint32_t f2 = 0x8899aabb; + uint32_t f3 = 0xccddeeff; + uint32_t value; + uint64_t pc; + uc_err err; + + sparc64_setup(&uc, code, sizeof(code)); + OK(uc_reg_write(uc, UC_SPARC_REG_G1, &gsr)); + OK(uc_reg_write(uc, UC_SPARC_REG_F0, &f0)); + OK(uc_reg_write(uc, UC_SPARC_REG_F1, &f1)); + OK(uc_reg_write(uc, UC_SPARC_REG_F2, &f2)); + OK(uc_reg_write(uc, UC_SPARC_REG_F3, &f3)); + + err = uc_emu_start(uc, sparc64_code_start, + sparc64_code_start + sizeof(code), 0, 4); + OK(uc_reg_read(uc, UC_SPARC_REG_PC, &pc)); + TEST_CHECK_(err == UC_ERR_OK, "err=%u pc=0x%016" PRIx64, + (unsigned int)err, pc); + + OK(uc_reg_read(uc, UC_SPARC_REG_F4, &value)); + TEST_CHECK_(value == 0x44cc55dd, "f4 = 0x%08x", value); + OK(uc_reg_read(uc, UC_SPARC_REG_F5, &value)); + TEST_CHECK_(value == 0x66ee77ff, "f5 = 0x%08x", value); + OK(uc_reg_read(uc, UC_SPARC_REG_F6, &value)); + TEST_CHECK_(value == 0xbb33aa22, "f6 = 0x%08x", value); + OK(uc_reg_read(uc, UC_SPARC_REG_F7, &value)); + TEST_CHECK_(value == 0x99118800, "f7 = 0x%08x", value); + + OK(uc_close(uc)); +} + +static void test_sparc32_fpu_fsr(void) +{ + const uint8_t code[] = { + 0x8d, 0xa0, 0x09, 0xa2, /* fdivs %f0,%f2,%f6 */ + 0xc1, 0x28, 0x40, 0x00, /* st %fsr,[%g1] */ + }; + uc_engine *uc; + uint32_t address = data_start; + uint32_t numerator = 0x3f800000; + uint32_t denominator = 0; + uint32_t result; + uint32_t psr; + uint8_t fsr_data[4]; + uint32_t fsr; + + OK(uc_open(UC_ARCH_SPARC, UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_mem_map(uc, data_start, data_len, UC_PROT_ALL)); + OK(uc_reg_read(uc, UC_SPARC_REG_PSR, &psr)); + psr |= (1 << 12) | (1 << 7); + OK(uc_reg_write(uc, UC_SPARC_REG_PSR, &psr)); + OK(uc_reg_write(uc, UC_SPARC_REG_G1, &address)); + OK(uc_reg_write(uc, UC_SPARC_REG_F0, &numerator)); + OK(uc_reg_write(uc, UC_SPARC_REG_F2, &denominator)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 2)); + + OK(uc_reg_read(uc, UC_SPARC_REG_F6, &result)); + TEST_CHECK_(result == 0x7f800000, "f6 = 0x%08x", result); + OK(uc_mem_read(uc, data_start, fsr_data, sizeof(fsr_data))); + fsr = load_be32(fsr_data); + TEST_CHECK_((fsr & 0x7f) == 0x42, "fsr = 0x%08x", fsr); + + OK(uc_close(uc)); +} + +static void test_sparc64_fpu_fsr(void) +{ + const uint8_t code[] = { + 0x8d, 0x80, 0x20, 0x04, /* wr %g0,4,%fprs */ + 0x8d, 0xa0, 0x09, 0xa2, /* fdivs %f0,%f2,%f6 */ + 0xc3, 0x28, 0x40, 0x00, /* stx %fsr,[%g1] */ + }; + uc_engine *uc; + uint64_t address = data_start; + uint32_t numerator = 0x3f800000; + uint32_t denominator = 0; + uint32_t result; + uint8_t fsr_data[8]; + uint64_t fsr; + + sparc64_setup(&uc, code, sizeof(code)); + OK(uc_mem_map(uc, data_start, data_len, UC_PROT_ALL)); + OK(uc_reg_write(uc, UC_SPARC_REG_G1, &address)); + OK(uc_reg_write(uc, UC_SPARC_REG_F0, &numerator)); + OK(uc_reg_write(uc, UC_SPARC_REG_F2, &denominator)); + + OK(uc_emu_start(uc, sparc64_code_start, + sparc64_code_start + sizeof(code), 0, 3)); + + OK(uc_reg_read(uc, UC_SPARC_REG_F6, &result)); + TEST_CHECK_(result == 0x7f800000, "f6 = 0x%08x", result); + OK(uc_mem_read(uc, data_start, fsr_data, sizeof(fsr_data))); + fsr = load_be64(fsr_data); + TEST_CHECK_((fsr & 0x7f) == 0x42, "fsr = 0x%016" PRIx64, fsr); + + OK(uc_close(uc)); +} + +static void test_sparc32_register_window_save_restore(void) +{ + const uint8_t code[] = { + 0x81, 0x90, 0x00, 0x00, /* wr %g0,%g0,%wim */ + 0x9d, 0xe3, 0xbf, 0xc0, /* save %sp,-64,%sp */ + 0x82, 0x10, 0x00, 0x19, /* mov %i1,%g1 */ + 0x81, 0xe8, 0x00, 0x00, /* restore */ + }; + uc_engine *uc; + uint32_t sp = 0x9000; + uint32_t o1 = 0x11223344; + uint32_t value; + + OK(uc_open(UC_ARCH_SPARC, UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_reg_write(uc, UC_SPARC_REG_SP, &sp)); + OK(uc_reg_write(uc, UC_SPARC_REG_O1, &o1)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + OK(uc_reg_read(uc, UC_SPARC_REG_G1, &value)); + TEST_CHECK_(value == o1, "g1 = 0x%08x", value); + OK(uc_reg_read(uc, UC_SPARC_REG_SP, &value)); + TEST_CHECK_(value == sp, "sp = 0x%08x", value); + OK(uc_reg_read(uc, UC_SPARC_REG_O1, &value)); + TEST_CHECK_(value == o1, "o1 = 0x%08x", value); + + OK(uc_close(uc)); +} + +static void test_sparc64_register_window_save_restore(void) +{ + const uint8_t code[] = { + 0x95, 0x90, 0x20, 0x06, /* wrpr %g0,6,%cansave */ + 0x97, 0x90, 0x20, 0x00, /* wrpr %g0,0,%canrestore */ + 0x99, 0x90, 0x20, 0x07, /* wrpr %g0,7,%cleanwin */ + 0x9d, 0xe3, 0xbf, 0x50, /* save %sp,-176,%sp */ + 0x82, 0x10, 0x00, 0x19, /* mov %i1,%g1 */ + 0x81, 0xe8, 0x00, 0x00, /* restore */ + }; + uc_engine *uc; + uint64_t sp = 0x9000; + uint64_t o1 = UINT64_C(0x1122334455667788); + uint64_t value; + + sparc64_setup(&uc, code, sizeof(code)); + OK(uc_reg_write(uc, UC_SPARC_REG_SP, &sp)); + OK(uc_reg_write(uc, UC_SPARC_REG_O1, &o1)); + + OK(uc_emu_start(uc, sparc64_code_start, + sparc64_code_start + sizeof(code), 0, 6)); + + OK(uc_reg_read(uc, UC_SPARC_REG_G1, &value)); + TEST_CHECK_(value == o1, "g1 = 0x%016" PRIx64, value); + OK(uc_reg_read(uc, UC_SPARC_REG_SP, &value)); + TEST_CHECK_(value == sp, "sp = 0x%016" PRIx64, value); + OK(uc_reg_read(uc, UC_SPARC_REG_O1, &value)); + TEST_CHECK_(value == o1, "o1 = 0x%016" PRIx64, value); + + OK(uc_close(uc)); +} + +static void test_sparc_window_trap_cb(uc_engine *uc, uint32_t intno, + void *data) +{ + SparcIntrCapture *capture = (SparcIntrCapture *)data; + + capture->count++; + capture->intno = intno; + OK(uc_emu_stop(uc)); +} + +static void test_sparc32_register_window_traps(void) +{ + static const struct { + uint32_t instruction; + uint32_t intno; + } cases[] = { + {0x9de3bfc0, 0x05}, /* save %sp,-64,%sp: window overflow */ + {0x81e80000, 0x06}, /* restore: window underflow */ + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + uint8_t code[8]; + SparcIntrCapture capture = {0}; + uc_engine *uc; + uc_hook hook; + uint32_t psr; + uint32_t wim = UINT32_MAX; + + store_be32(code, 0x81900001); /* wr %g0,%g1,%wim */ + store_be32(code + 4, cases[i].instruction); + OK(uc_open(UC_ARCH_SPARC, + UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_sparc_window_trap_cb, + &capture, 1, 0)); + OK(uc_reg_read(uc, UC_SPARC_REG_PSR, &psr)); + psr = (psr & ~0x1f) | (1 << 7); + OK(uc_reg_write(uc, UC_SPARC_REG_PSR, &psr)); + OK(uc_reg_write(uc, UC_SPARC_REG_G1, &wim)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + + TEST_CHECK_(capture.count == 1, "case=%u interrupts=%u", + (unsigned int)i, capture.count); + TEST_CHECK_(capture.intno == cases[i].intno, "case=%u intno=0x%x", + (unsigned int)i, capture.intno); + OK(uc_reg_read(uc, UC_SPARC_REG_PSR, &psr)); + TEST_CHECK_((psr & 0x1f) == 0, "case=%u cwp=%u", (unsigned int)i, + psr & 0x1f); + + OK(uc_close(uc)); + } +} + +static void test_sparc64_register_window_traps(void) +{ + static const struct { + uint32_t window_state_instruction; + uint32_t instruction; + uint32_t intno; + } cases[] = { + {0x95902000, 0x9de3bf50, 0x80}, /* cansave=0; save: spill */ + {0x97902000, 0x81e80000, 0xc0}, /* canrestore=0; restore: fill */ + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + uint8_t code[16]; + SparcIntrCapture capture = {0}; + uc_engine *uc; + uc_hook hook; + + store_be32(code, cases[i].window_state_instruction); + store_be32(code + 4, 0x9b902000); /* wrpr %g0,0,%otherwin */ + store_be32(code + 8, 0x9d902000); /* wrpr %g0,0,%wstate */ + store_be32(code + 12, cases[i].instruction); + sparc64_setup(&uc, code, sizeof(code)); + OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_sparc_window_trap_cb, + &capture, 1, 0)); + + OK(uc_emu_start(uc, sparc64_code_start, + sparc64_code_start + sizeof(code), 0, 0)); + + TEST_CHECK_(capture.count == 1, "case=%u interrupts=%u", + (unsigned int)i, capture.count); + TEST_CHECK_(capture.intno == cases[i].intno, "case=%u intno=0x%x", + (unsigned int)i, capture.intno); + + OK(uc_close(uc)); + } +} + +static void test_sparc32_asi_swap(void) +{ + const uint8_t code[] = { + 0xc4, 0x80, 0x40, 0x80, /* lda [%g1] 4,%g2 */ + 0xc6, 0x78, 0x40, 0x00, /* swap [%g1],%g3 */ + }; + uint8_t memory[4]; + uc_engine *uc; + uint32_t address = data_start; + uint32_t replacement = 0xaabbccdd; + uint32_t psr; + uint32_t value; + + OK(uc_open(UC_ARCH_SPARC, UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN, &uc)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_mem_map(uc, data_start, data_len, UC_PROT_ALL)); + store_be32(memory, 0x11223344); + OK(uc_mem_write(uc, data_start, memory, sizeof(memory))); + OK(uc_reg_read(uc, UC_SPARC_REG_PSR, &psr)); + psr |= 1 << 7; + OK(uc_reg_write(uc, UC_SPARC_REG_PSR, &psr)); + OK(uc_reg_write(uc, UC_SPARC_REG_G1, &address)); + OK(uc_reg_write(uc, UC_SPARC_REG_G3, &replacement)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 2)); + + OK(uc_reg_read(uc, UC_SPARC_REG_G2, &value)); + TEST_CHECK_(value == 0xf3000000, "g2 = 0x%08x", value); + OK(uc_reg_read(uc, UC_SPARC_REG_G3, &value)); + TEST_CHECK_(value == 0x11223344, "g3 = 0x%08x", value); + OK(uc_mem_read(uc, data_start, memory, sizeof(memory))); + value = load_be32(memory); + TEST_CHECK_(value == replacement, "memory = 0x%08x", value); + + OK(uc_close(uc)); +} + +static void test_sparc64_asi_swap(void) +{ + const uint8_t code[] = { + 0xc4, 0x80, 0x50, 0x00, /* lduwa [%g1] 0x80,%g2 */ + 0xc6, 0xf8, 0x50, 0x00, /* swapa [%g1] 0x80,%g3 */ + }; + uint8_t memory[4]; + uc_engine *uc; + uint64_t address = data_start; + uint64_t replacement = 0xaabbccdd; + uint64_t value; + + sparc64_setup(&uc, code, sizeof(code)); + OK(uc_mem_map(uc, data_start, data_len, UC_PROT_ALL)); + store_be32(memory, 0x11223344); + OK(uc_mem_write(uc, data_start, memory, sizeof(memory))); + OK(uc_reg_write(uc, UC_SPARC_REG_G1, &address)); + OK(uc_reg_write(uc, UC_SPARC_REG_G3, &replacement)); + + OK(uc_emu_start(uc, sparc64_code_start, + sparc64_code_start + sizeof(code), 0, 2)); + + OK(uc_reg_read(uc, UC_SPARC_REG_G2, &value)); + TEST_CHECK_(value == 0x11223344, "g2 = 0x%016" PRIx64, value); + OK(uc_reg_read(uc, UC_SPARC_REG_G3, &value)); + TEST_CHECK_(value == 0x11223344, "g3 = 0x%016" PRIx64, value); + OK(uc_mem_read(uc, data_start, memory, sizeof(memory))); + value = load_be32(memory); + TEST_CHECK_(value == replacement, "memory = 0x%016" PRIx64, value); + + OK(uc_close(uc)); +} + +static void test_sparc64_branch_annul(void) +{ + static const struct { + uint32_t xcc; + uint64_t g2; + uint64_t g3; + } cases[] = { + {4, 1, 0}, + {0, 0, 2}, + }; + const uint8_t code[] = { + 0x22, 0x68, 0x00, 0x03, /* be,a %xcc,sparc64_code_start+12 */ + 0x84, 0x10, 0x20, 0x01, /* mov 1,%g2 */ + 0x86, 0x10, 0x20, 0x02, /* mov 2,%g3 */ + 0x88, 0x10, 0x20, 0x03, /* mov 3,%g4 */ + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + uc_engine *uc; + uint64_t value; + + sparc64_setup(&uc, code, sizeof(code)); + OK(uc_reg_write(uc, UC_SPARC_REG_XCC, &cases[i].xcc)); + OK(uc_emu_start(uc, sparc64_code_start, + sparc64_code_start + sizeof(code), 0, 2)); + + OK(uc_reg_read(uc, UC_SPARC_REG_G2, &value)); + TEST_CHECK_(value == cases[i].g2, "case=%u g2=%" PRIu64, + (unsigned int)i, value); + OK(uc_reg_read(uc, UC_SPARC_REG_G3, &value)); + TEST_CHECK_(value == cases[i].g3, "case=%u g3=%" PRIu64, + (unsigned int)i, value); + OK(uc_reg_read(uc, UC_SPARC_REG_PC, &value)); + TEST_CHECK_(value == sparc64_code_start + 12, + "case=%u pc=0x%016" PRIx64, (unsigned int)i, value); + + OK(uc_close(uc)); + } +} + TEST_LIST = { - {"test_virtual_read", test_virtual_read}, - {"test_sparc32_public_registers", test_sparc32_public_registers}, - {"test_sparc64_public_registers", test_sparc64_public_registers}, - {"test_sparc32_unaligned_access_sets_fault_address", - test_sparc32_unaligned_access_sets_fault_address}, - {NULL, NULL} -}; + {"test_sparc64_engine_lifecycle", test_sparc64_engine_lifecycle}, + {"test_sparc64_bpr_reserved_condition", + test_sparc64_bpr_reserved_condition}, + {"test_virtual_read", test_virtual_read}, + {"test_sparc32_public_registers", test_sparc32_public_registers}, + {"test_sparc64_public_registers", test_sparc64_public_registers}, + {"test_sparc32_unaligned_access_sets_fault_address", + test_sparc32_unaligned_access_sets_fault_address}, + {"test_sparc32_delay_slot_count", test_sparc32_delay_slot_count}, + {"test_sparc32_delay_slot_pending_stop", + test_sparc32_delay_slot_pending_stop}, + {"test_sparc32_branch_always_annul", test_sparc32_branch_always_annul}, + {"test_sparc32_context_roundtrip", test_sparc32_context_roundtrip}, + {"test_sparc64_vis1_vis2", test_sparc64_vis1_vis2}, + {"test_sparc32_fpu_fsr", test_sparc32_fpu_fsr}, + {"test_sparc64_fpu_fsr", test_sparc64_fpu_fsr}, + {"test_sparc32_register_window_save_restore", + test_sparc32_register_window_save_restore}, + {"test_sparc64_register_window_save_restore", + test_sparc64_register_window_save_restore}, + {"test_sparc32_register_window_traps", + test_sparc32_register_window_traps}, + {"test_sparc64_register_window_traps", + test_sparc64_register_window_traps}, + {"test_sparc32_asi_swap", test_sparc32_asi_swap}, + {"test_sparc64_asi_swap", test_sparc64_asi_swap}, + {"test_sparc64_branch_annul", test_sparc64_branch_annul}, + {NULL, NULL}}; diff --git a/tests/unit/test_tricore.c b/tests/unit/test_tricore.c index 5669d54fdc..4a9ec1b004 100644 --- a/tests/unit/test_tricore.c +++ b/tests/unit/test_tricore.c @@ -2,6 +2,24 @@ const uint64_t code_start = 0x10000; const uint64_t code_len = 0x4000; +const uint64_t csa_start = 0x20000; +const uint64_t csa_len = 0x4000; +const uint64_t data_start = 0x30000; +const uint64_t data_len = 0x4000; + +static uint32_t load_le32(const uint8_t *data) +{ + return (uint32_t)data[0] | ((uint32_t)data[1] << 8) | + ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24); +} + +static void store_le32(uint8_t *data, uint32_t value) +{ + data[0] = value; + data[1] = value >> 8; + data[2] = value >> 16; + data[3] = value >> 24; +} static void uc_map_code(uc_engine *uc, const uint8_t *code, size_t size) { @@ -15,11 +33,57 @@ static void uc_common_setup(uc_engine **uc, const uint8_t *code, size_t size) uc_map_code(*uc, code, size); } +static uint32_t tricore_rr(uint8_t major, uint8_t op2, uint8_t dest, + uint8_t src1, uint8_t src2) +{ + return major | ((uint32_t)src1 << 8) | ((uint32_t)src2 << 12) | + ((uint32_t)op2 << 20) | ((uint32_t)dest << 28); +} + +static uint32_t tricore_bo(uint8_t major, uint8_t op2, uint8_t reg, + uint8_t base, int16_t offset) +{ + uint32_t off10 = (uint16_t)offset & 0x3ff; + + return major | ((uint32_t)reg << 8) | ((uint32_t)base << 12) | + ((off10 & 0x3f) << 16) | ((uint32_t)op2 << 22) | + ((off10 >> 6) << 28); +} + +static uint32_t tricore_bol(uint8_t major, uint8_t reg, uint8_t base, + int16_t offset) +{ + uint32_t off16 = (uint16_t)offset; + + return major | ((uint32_t)reg << 8) | ((uint32_t)base << 12) | + ((off16 & 0x3f) << 16) | (((off16 >> 10) & 0x3f) << 22) | + (((off16 >> 6) & 0xf) << 28); +} + +static uint32_t tricore_brc(uint8_t major, bool op2, uint8_t src, + int8_t constant, int16_t displacement) +{ + return major | ((uint32_t)src << 8) | (((uint32_t)constant & 0xf) << 12) | + (((uint32_t)displacement & 0x7fff) << 16) | ((uint32_t)op2 << 31); +} + +static void append_insn32(uint8_t *code, size_t *offset, uint32_t insn) +{ + store_le32(code + *offset, insn); + *offset += 4; +} + +static void append_insn16(uint8_t *code, size_t *offset, uint16_t insn) +{ + code[*offset] = insn; + code[*offset + 1] = insn >> 8; + *offset += 2; +} + static void test_tricore_mov_dreg(void) { const uint8_t code[] = { - 0x82, 0x11, - 0xbb, 0x00, 0x00, 0x08, + 0x82, 0x11, 0xbb, 0x00, 0x00, 0x08, }; uc_engine *uc; uint32_t d0 = 0; @@ -82,14 +146,634 @@ static void test_tricore_cpu_model(void) OK(uc_open(UC_ARCH_TRICORE, UC_MODE_LITTLE_ENDIAN, &uc)); OK(uc_ctl_set_cpu_model(uc, UC_CPU_TRICORE_TC1796)); - TEST_CHECK(uc_ctl_set_cpu_model(uc, UC_CPU_TRICORE_ENDING) == - UC_ERR_ARG); + TEST_CHECK(uc_ctl_set_cpu_model(uc, UC_CPU_TRICORE_ENDING) == UC_ERR_ARG); + OK(uc_close(uc)); +} + +static void test_tricore_fast_count_boundary(void) +{ + const uint8_t code[] = { + 0x82, 0x11, /* mov d1, #1 */ + 0xbb, 0x00, 0x00, 0x08, /* mov.u d0, #0x8000 */ + }; + uc_engine *uc; + uint32_t d0 = 0; + uint32_t d1 = 0; + uint32_t pc = 0; + + uc_common_setup(&uc, code, sizeof(code)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 1)); + OK(uc_reg_read(uc, UC_TRICORE_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_TRICORE_REG_D1, &d1)); + OK(uc_reg_read(uc, UC_TRICORE_REG_PC, &pc)); + TEST_CHECK(d0 == 0); + TEST_CHECK(d1 == 1); + TEST_CHECK(pc == (uint32_t)(code_start + 2)); + + d1 = 0; + OK(uc_reg_write(uc, UC_TRICORE_REG_D1, &d1)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 2)); + OK(uc_reg_read(uc, UC_TRICORE_REG_D0, &d0)); + OK(uc_reg_read(uc, UC_TRICORE_REG_D1, &d1)); + OK(uc_reg_read(uc, UC_TRICORE_REG_PC, &pc)); + TEST_CHECK(d0 == 0x8000); + TEST_CHECK(d1 == 1); + TEST_CHECK(pc == (uint32_t)(code_start + sizeof(code))); + + OK(uc_close(uc)); +} + +static void test_tricore_context_roundtrip(void) +{ + uc_engine *uc; + uc_context *context; + uint32_t d0 = 0x11223344; + uint32_t pc = code_start + 0x20; + uint32_t changed = 0; + + OK(uc_open(UC_ARCH_TRICORE, UC_MODE_LITTLE_ENDIAN, &uc)); + OK(uc_reg_write(uc, UC_TRICORE_REG_D0, &d0)); + OK(uc_reg_write(uc, UC_TRICORE_REG_PC, &pc)); + OK(uc_context_alloc(uc, &context)); + OK(uc_context_save(uc, context)); + + OK(uc_reg_write(uc, UC_TRICORE_REG_D0, &changed)); + OK(uc_reg_write(uc, UC_TRICORE_REG_PC, &changed)); + OK(uc_context_restore(uc, context)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_D0, &changed)); + TEST_CHECK(changed == d0); + OK(uc_reg_read(uc, UC_TRICORE_REG_PC, &changed)); + TEST_CHECK(changed == pc); + + OK(uc_context_free(context)); OK(uc_close(uc)); } +static void test_tricore_call_ret_csa(void) +{ + const uint8_t code[] = { + 0x6d, 0x00, 0x04, 0x00, /* call #code_start + 8 */ + 0x00, 0x09, /* nop */ + 0x00, 0x09, /* nop */ + 0x0d, 0x00, 0x80, 0x01, /* ret */ + }; + const int regs[] = { + UC_TRICORE_REG_A10, UC_TRICORE_REG_A11, UC_TRICORE_REG_D8, + UC_TRICORE_REG_D9, UC_TRICORE_REG_D10, UC_TRICORE_REG_D11, + UC_TRICORE_REG_A12, UC_TRICORE_REG_A13, UC_TRICORE_REG_A14, + UC_TRICORE_REG_A15, UC_TRICORE_REG_D12, UC_TRICORE_REG_D13, + UC_TRICORE_REG_D14, UC_TRICORE_REG_D15, + }; + const uint32_t values[] = { + 0xa10a10a1, 0xa11a11a1, 0xd08d08d0, 0xd09d09d0, 0xd10d10d1, + 0xd11d11d1, 0xa12a12a1, 0xa13a13a1, 0xa14a14a1, 0xa15a15a1, + 0xd12d12d1, 0xd13d13d1, 0xd14d14d1, 0xd15d15d1, + }; + const uint32_t csa_first = csa_start >> 6; + const uint32_t csa_second = (csa_start + 64) >> 6; + const uint32_t csa_limit = (csa_start + 128) >> 6; + const uint32_t initial_pcxi = 0; + const uint32_t initial_psw = 0xb80; + uint8_t frame[64]; + uc_engine *uc; + uint32_t value; + size_t i; + + memset(frame, 0xcc, sizeof(frame)); + store_le32(frame, csa_second); + + uc_common_setup(&uc, code, sizeof(code)); + OK(uc_mem_map(uc, csa_start, csa_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, csa_start, frame, sizeof(frame))); + OK(uc_reg_write(uc, UC_TRICORE_REG_FCX, &csa_first)); + OK(uc_reg_write(uc, UC_TRICORE_REG_LCX, &csa_limit)); + OK(uc_reg_write(uc, UC_TRICORE_REG_PCXI, &initial_pcxi)); + OK(uc_reg_write(uc, UC_TRICORE_REG_PSW, &initial_psw)); + for (i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) { + OK(uc_reg_write(uc, regs[i], &values[i])); + } + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 1)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_PC, &value)); + TEST_CHECK(value == code_start + 8); + OK(uc_reg_read(uc, UC_TRICORE_REG_A11, &value)); + TEST_CHECK(value == code_start + 4); + OK(uc_reg_read(uc, UC_TRICORE_REG_FCX, &value)); + TEST_CHECK(value == csa_second); + OK(uc_reg_read(uc, UC_TRICORE_REG_PCXI, &value)); + TEST_CHECK(value == (0x00400000 | csa_first)); + + OK(uc_mem_read(uc, csa_start, frame, sizeof(frame))); + TEST_CHECK(load_le32(frame) == initial_pcxi); + TEST_CHECK(load_le32(frame + 4) == initial_psw); + for (i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) { + TEST_CHECK(load_le32(frame + 8 + i * 4) == values[i]); + if (regs[i] != UC_TRICORE_REG_A11) { + value = 0x5a5a0000 + i; + OK(uc_reg_write(uc, regs[i], &value)); + } + } + + OK(uc_emu_start(uc, code_start + 8, code_start + sizeof(code), 0, 1)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_PC, &value)); + TEST_CHECK(value == code_start + 4); + OK(uc_reg_read(uc, UC_TRICORE_REG_FCX, &value)); + TEST_CHECK(value == csa_first); + OK(uc_reg_read(uc, UC_TRICORE_REG_PCXI, &value)); + TEST_CHECK(value == initial_pcxi); + OK(uc_reg_read(uc, UC_TRICORE_REG_PSW, &value)); + TEST_CHECK(value == initial_psw); + for (i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) { + OK(uc_reg_read(uc, regs[i], &value)); + TEST_CHECK(value == values[i]); + } + OK(uc_mem_read(uc, csa_start, frame, sizeof(frame))); + TEST_CHECK(load_le32(frame) == csa_second); + + OK(uc_close(uc)); +} + +static void test_tricore_svlcx_rslcx_csa(void) +{ + const uint8_t code[] = { + 0x0d, 0x00, 0x00, 0x02, /* svlcx */ + 0x0d, 0x00, 0x40, 0x02, /* rslcx */ + }; + const int regs[] = { + UC_TRICORE_REG_A11, UC_TRICORE_REG_A2, UC_TRICORE_REG_A3, + UC_TRICORE_REG_D0, UC_TRICORE_REG_D1, UC_TRICORE_REG_D2, + UC_TRICORE_REG_D3, UC_TRICORE_REG_A4, UC_TRICORE_REG_A5, + UC_TRICORE_REG_A6, UC_TRICORE_REG_A7, UC_TRICORE_REG_D4, + UC_TRICORE_REG_D5, UC_TRICORE_REG_D6, UC_TRICORE_REG_D7, + }; + const uint32_t values[] = { + 0xa11a11a1, 0xa02a02a0, 0xa03a03a0, 0xd00d00d0, 0xd01d01d0, + 0xd02d02d0, 0xd03d03d0, 0xa04a04a0, 0xa05a05a0, 0xa06a06a0, + 0xa07a07a0, 0xd04d04d0, 0xd05d05d0, 0xd06d06d0, 0xd07d07d0, + }; + const uint32_t csa_first = csa_start >> 6; + const uint32_t csa_second = (csa_start + 64) >> 6; + const uint32_t csa_limit = (csa_start + 128) >> 6; + const uint32_t initial_pcxi = 0; + uint8_t frame[64]; + uc_engine *uc; + uint32_t value; + size_t i; + + memset(frame, 0xcc, sizeof(frame)); + store_le32(frame, csa_second); + + uc_common_setup(&uc, code, sizeof(code)); + OK(uc_mem_map(uc, csa_start, csa_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, csa_start, frame, sizeof(frame))); + OK(uc_reg_write(uc, UC_TRICORE_REG_FCX, &csa_first)); + OK(uc_reg_write(uc, UC_TRICORE_REG_LCX, &csa_limit)); + OK(uc_reg_write(uc, UC_TRICORE_REG_PCXI, &initial_pcxi)); + for (i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) { + OK(uc_reg_write(uc, regs[i], &values[i])); + } + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 1)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_PC, &value)); + TEST_CHECK(value == code_start + 4); + OK(uc_reg_read(uc, UC_TRICORE_REG_FCX, &value)); + TEST_CHECK(value == csa_second); + OK(uc_reg_read(uc, UC_TRICORE_REG_PCXI, &value)); + TEST_CHECK(value == csa_first); + OK(uc_mem_read(uc, csa_start, frame, sizeof(frame))); + TEST_CHECK(load_le32(frame) == initial_pcxi); + for (i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) { + TEST_CHECK(load_le32(frame + 4 + i * 4) == values[i]); + value = 0x5a5a0000 + i; + OK(uc_reg_write(uc, regs[i], &value)); + } + + OK(uc_emu_start(uc, code_start + 4, code_start + sizeof(code), 0, 1)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_PC, &value)); + TEST_CHECK(value == code_start + sizeof(code)); + OK(uc_reg_read(uc, UC_TRICORE_REG_FCX, &value)); + TEST_CHECK(value == csa_first); + OK(uc_reg_read(uc, UC_TRICORE_REG_PCXI, &value)); + TEST_CHECK(value == initial_pcxi); + for (i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) { + OK(uc_reg_read(uc, regs[i], &value)); + TEST_CHECK(value == values[i]); + } + OK(uc_mem_read(uc, csa_start, frame, sizeof(frame))); + TEST_CHECK(load_le32(frame) == csa_second); + + OK(uc_close(uc)); +} + +static void test_tricore_csa_exhaustion(void) +{ + const uint8_t code[] = { + 0x0d, + 0x00, + 0x00, + 0x02, /* svlcx */ + }; + uc_engine *uc; + uc_err err; + uint32_t btv = 0x30000; + uint32_t fcx = 0; + uint32_t value; + + uc_common_setup(&uc, code, sizeof(code)); + OK(uc_reg_write(uc, UC_TRICORE_REG_BTV, &btv)); + OK(uc_reg_write(uc, UC_TRICORE_REG_FCX, &fcx)); + err = uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0); + TEST_CHECK_(err == UC_ERR_FETCH_UNMAPPED, "err = %u", (unsigned)err); + OK(uc_reg_read(uc, UC_TRICORE_REG_PC, &value)); + TEST_CHECK(value == btv + 0x60); + OK(uc_reg_read(uc, UC_TRICORE_REG_D15, &value)); + TEST_CHECK(value == 4); + OK(uc_reg_read(uc, UC_TRICORE_REG_A11, &value)); + TEST_CHECK(value == code_start); + OK(uc_reg_read(uc, UC_TRICORE_REG_FCX, &value)); + TEST_CHECK(value == 0); + + OK(uc_close(uc)); +} + +static void test_tricore_integer_memory_branch(void) +{ + uint8_t code[24] = {0}; + size_t offset = 0; + uc_engine *uc; + uint32_t value; + + append_insn32(code, &offset, + tricore_rr(0x0b, 0x00, 3, 1, 2)); /* add d3,d1,d2 */ + append_insn32(code, &offset, tricore_bol(0x59, 3, 2, 0)); /* st.w [a2],d3 */ + append_insn32(code, &offset, tricore_bol(0x19, 4, 2, 0)); /* ld.w d4,[a2] */ + append_insn32(code, &offset, + tricore_brc(0xdf, false, 4, 7, 4)); /* jeq d4,7,+8 */ + append_insn16(code, &offset, 0x1582); /* mov d5,1 */ + append_insn16(code, &offset, 0x0900); /* nop */ + append_insn16(code, &offset, 0x2682); /* mov d6,2 */ + + uc_common_setup(&uc, code, offset); + OK(uc_mem_map(uc, data_start, data_len, UC_PROT_ALL)); + value = 3; + OK(uc_reg_write(uc, UC_TRICORE_REG_D1, &value)); + value = 4; + OK(uc_reg_write(uc, UC_TRICORE_REG_D2, &value)); + value = data_start; + OK(uc_reg_write(uc, UC_TRICORE_REG_A2, &value)); + + OK(uc_emu_start(uc, code_start, code_start + offset, 0, 0)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_D3, &value)); + TEST_CHECK_(value == 7, "d3 = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_D4, &value)); + TEST_CHECK_(value == 7, "d4 = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_D5, &value)); + TEST_CHECK_(value == 0, "d5 = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_D6, &value)); + TEST_CHECK_(value == 2, "d6 = 0x%08x", value); + OK(uc_mem_read(uc, data_start, &value, sizeof(value))); + TEST_CHECK_(value == 7, "memory = 0x%08x", value); + + OK(uc_close(uc)); +} + +static void test_tricore_saturation_and_flags(void) +{ + uint8_t code[8]; + size_t offset = 0; + uc_engine *uc; + uint32_t value; + + append_insn32(code, &offset, + tricore_rr(0x0b, 0x02, 3, 1, 2)); /* adds d3,d1,d2 */ + append_insn32(code, &offset, + tricore_rr(0x0b, 0x5e, 4, 5, 0)); /* sat.b d4,d5 */ + + uc_common_setup(&uc, code, offset); + value = 0x7fffffff; + OK(uc_reg_write(uc, UC_TRICORE_REG_D1, &value)); + value = 1; + OK(uc_reg_write(uc, UC_TRICORE_REG_D2, &value)); + value = 0x180; + OK(uc_reg_write(uc, UC_TRICORE_REG_D5, &value)); + + OK(uc_emu_start(uc, code_start, code_start + offset, 0, 0)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_D3, &value)); + TEST_CHECK_(value == 0x7fffffff, "d3 = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_D4, &value)); + TEST_CHECK_(value == 0x7f, "d4 = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_PSW_USB_V, &value)); + TEST_CHECK_(value == 0x80000000, "V = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_PSW_USB_SV, &value)); + TEST_CHECK_(value == 0x80000000, "SV = 0x%08x", value); + + OK(uc_close(uc)); +} + +static void test_tricore_circular_addressing(void) +{ + uint8_t code[8]; + size_t offset = 0; + const uint32_t words[] = {0x11223344, 0x55667788}; + uc_engine *uc; + uint32_t value; + + append_insn32(code, &offset, tricore_bo(0x29, 0x14, 1, 2, 4)); + append_insn32(code, &offset, tricore_bo(0x29, 0x14, 2, 2, 4)); + + uc_common_setup(&uc, code, offset); + OK(uc_mem_map(uc, data_start, data_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_start, words, sizeof(words))); + value = data_start; + OK(uc_reg_write(uc, UC_TRICORE_REG_A2, &value)); + value = (8u << 16) | 4; + OK(uc_reg_write(uc, UC_TRICORE_REG_A3, &value)); + + OK(uc_emu_start(uc, code_start, code_start + offset, 0, 0)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_D1, &value)); + TEST_CHECK_(value == words[1], "d1 = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_D2, &value)); + TEST_CHECK_(value == words[0], "d2 = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_A3, &value)); + TEST_CHECK_(value == ((8u << 16) | 4), "a3 = 0x%08x", value); + + OK(uc_close(uc)); +} + +static void test_tricore_bit_reverse_addressing(void) +{ + const uint8_t bytes[] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + }; + uint8_t code[16]; + size_t offset = 0; + uc_engine *uc; + uint32_t value; + + append_insn32(code, &offset, tricore_bo(0x29, 0x01, 0, 2, 0)); + append_insn32(code, &offset, tricore_bo(0x29, 0x01, 1, 2, 0)); + append_insn32(code, &offset, tricore_bo(0x29, 0x01, 2, 2, 0)); + append_insn32(code, &offset, tricore_bo(0x29, 0x01, 3, 2, 0)); + + uc_common_setup(&uc, code, offset); + OK(uc_mem_map(uc, data_start, data_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_start, bytes, sizeof(bytes))); + value = data_start; + OK(uc_reg_write(uc, UC_TRICORE_REG_A2, &value)); + value = 4u << 16; + OK(uc_reg_write(uc, UC_TRICORE_REG_A3, &value)); + + OK(uc_emu_start(uc, code_start, code_start + offset, 0, 0)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_D0, &value)); + TEST_CHECK_(value == bytes[0], "d0 = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_D1, &value)); + TEST_CHECK_(value == bytes[4], "d1 = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_D2, &value)); + TEST_CHECK_(value == bytes[2], "d2 = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_D3, &value)); + TEST_CHECK_(value == bytes[6], "d3 = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_A3, &value)); + TEST_CHECK_(value == ((4u << 16) | 1), "a3 = 0x%08x", value); + + OK(uc_close(uc)); +} + +static void test_tricore_fpu_rounding_and_flags(void) +{ + uint8_t code[8]; + size_t offset = 0; + uc_engine *uc; + uint32_t value = 0x3fc00000; /* 1.5f */ + uint32_t psw; + + append_insn32(code, &offset, + tricore_rr(0x4b, 0x10, 2, 1, 0)); /* ftoi d2,d1 */ + append_insn32(code, &offset, + tricore_rr(0x4b, 0x13, 3, 1, 0)); /* ftoiz d3,d1 */ + + uc_common_setup(&uc, code, offset); + OK(uc_reg_write(uc, UC_TRICORE_REG_D1, &value)); + OK(uc_emu_start(uc, code_start, code_start + offset, 0, 0)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_D2, &value)); + TEST_CHECK_(value == 2, "ftoi result=%u", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_D3, &value)); + TEST_CHECK_(value == 1, "ftoiz result=%u", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_PSW, &psw)); + TEST_CHECK_((psw & 0x84000000) == 0x84000000, "psw=0x%08x", psw); + + OK(uc_close(uc)); +} + +static void test_tricore_fpu_divide_by_zero_status(void) +{ + uint8_t code[4]; + size_t offset = 0; + uc_engine *uc; + uint32_t value; + + append_insn32(code, &offset, + tricore_rr(0x4b, 0x05, 3, 1, 2)); /* div.f d3,d1,d2 */ + + uc_common_setup(&uc, code, offset); + value = 0x3f800000; /* 1.0f */ + OK(uc_reg_write(uc, UC_TRICORE_REG_D1, &value)); + value = 0; + OK(uc_reg_write(uc, UC_TRICORE_REG_D2, &value)); + + OK(uc_emu_start(uc, code_start, code_start + offset, 0, 0)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_D3, &value)); + TEST_CHECK_(value == 0x7f800000, "result = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_PSW_USB_C, &value)); + TEST_CHECK_(value == 1, "FS = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_PSW_USB_V, &value)); + TEST_CHECK_(value == 0, "FI = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_PSW_USB_SV, &value)); + TEST_CHECK_(value == 0, "FV = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_PSW_USB_AV, &value)); + TEST_CHECK_(value == 0x80000000, "FZ = 0x%08x", value); + OK(uc_reg_read(uc, UC_TRICORE_REG_PSW_USB_SAV, &value)); + TEST_CHECK_(value == 0, "FU = 0x%08x", value); + + OK(uc_close(uc)); +} + +static void test_tricore_swap_word(void) +{ + uint8_t code[4]; + size_t offset = 0; + uc_engine *uc; + uint32_t value; + + append_insn32(code, &offset, + tricore_bo(0x49, 0x20, 0, 2, 0)); /* swap.w [a2],d0 */ + uc_common_setup(&uc, code, offset); + OK(uc_mem_map(uc, data_start, data_len, UC_PROT_ALL)); + value = 0x11223344; + OK(uc_mem_write(uc, data_start, &value, sizeof(value))); + value = data_start; + OK(uc_reg_write(uc, UC_TRICORE_REG_A2, &value)); + value = 0xaabbccdd; + OK(uc_reg_write(uc, UC_TRICORE_REG_D0, &value)); + + OK(uc_emu_start(uc, code_start, code_start + offset, 0, 0)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_D0, &value)); + TEST_CHECK_(value == 0x11223344, "d0 = 0x%08x", value); + OK(uc_mem_read(uc, data_start, &value, sizeof(value))); + TEST_CHECK_(value == 0xaabbccdd, "memory = 0x%08x", value); + + OK(uc_close(uc)); +} + +static void test_tricore_cmpswap_word(void) +{ + static const struct { + uint32_t compare; + uint32_t expected_memory; + } cases[] = { + {0x11223344, 0xaabbccdd}, + {0x55667788, 0x11223344}, + }; + uint8_t code[4]; + size_t offset = 0; + size_t i; + + append_insn32(code, &offset, + tricore_bo(0x49, 0x23, 0, 2, 0)); /* cmpswap.w [a2],e0 */ + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + uint8_t memory[4]; + uc_engine *uc; + uint32_t value; + + uc_common_setup(&uc, code, offset); + OK(uc_mem_map(uc, data_start, data_len, UC_PROT_ALL)); + store_le32(memory, 0x11223344); + OK(uc_mem_write(uc, data_start, memory, sizeof(memory))); + value = data_start; + OK(uc_reg_write(uc, UC_TRICORE_REG_A2, &value)); + value = 0xaabbccdd; + OK(uc_reg_write(uc, UC_TRICORE_REG_D0, &value)); + OK(uc_reg_write(uc, UC_TRICORE_REG_D1, &cases[i].compare)); + + OK(uc_emu_start(uc, code_start, code_start + offset, 0, 0)); + + OK(uc_reg_read(uc, UC_TRICORE_REG_D0, &value)); + TEST_CHECK_(value == 0x11223344, "case=%u d0=0x%08x", + (unsigned int)i, value); + OK(uc_reg_read(uc, UC_TRICORE_REG_D1, &value)); + TEST_CHECK_(value == cases[i].compare, "case=%u d1=0x%08x", + (unsigned int)i, value); + OK(uc_mem_read(uc, data_start, memory, sizeof(memory))); + value = load_le32(memory); + TEST_CHECK_(value == cases[i].expected_memory, + "case=%u memory=0x%08x", (unsigned int)i, value); + + OK(uc_close(uc)); + } +} + +static void setup_tricore_exception_context(uc_engine *uc) +{ + uint8_t frame[64] = {0}; + uint32_t fcx = csa_start >> 6; + uint32_t lcx = (csa_start + 64) >> 6; + uint32_t btv = data_start; + + OK(uc_mem_map(uc, csa_start, csa_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, csa_start, frame, sizeof(frame))); + OK(uc_reg_write(uc, UC_TRICORE_REG_FCX, &fcx)); + OK(uc_reg_write(uc, UC_TRICORE_REG_LCX, &lcx)); + OK(uc_reg_write(uc, UC_TRICORE_REG_BTV, &btv)); +} + +static void test_tricore_crc32_model_gating(void) +{ + static const struct { + int model; + bool supported; + } cases[] = { + {UC_CPU_TRICORE_TC1796, false}, + {UC_CPU_TRICORE_TC1797, false}, + {UC_CPU_TRICORE_TC27X, true}, + }; + uint8_t code[4]; + size_t offset = 0; + size_t i; + + append_insn32(code, &offset, + tricore_rr(0x4b, 0x03, 3, 1, 2)); /* crc32 d3,d1,d2 */ + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + uc_engine *uc; + uc_err err; + uint32_t value = 0x12345678; + uint32_t pc; + + OK(uc_open(UC_ARCH_TRICORE, UC_MODE_LITTLE_ENDIAN, &uc)); + OK(uc_ctl_set_cpu_model(uc, cases[i].model)); + uc_map_code(uc, code, offset); + OK(uc_reg_write(uc, UC_TRICORE_REG_D1, &value)); + value = 0x89abcdef; + OK(uc_reg_write(uc, UC_TRICORE_REG_D2, &value)); + + if (!cases[i].supported) { + setup_tricore_exception_context(uc); + } + err = uc_emu_start(uc, code_start, code_start + offset, 0, 0); + + if (cases[i].supported) { + TEST_CHECK_(err == UC_ERR_OK, "model=%d err=%u", cases[i].model, + (unsigned int)err); + OK(uc_reg_read(uc, UC_TRICORE_REG_D3, &value)); + TEST_CHECK_(value == 0x84f50443, "model=%d crc=0x%08x", + cases[i].model, value); + } else { + TEST_CHECK_(err == UC_ERR_FETCH_UNMAPPED, "model=%d err=%u", + cases[i].model, (unsigned int)err); + OK(uc_reg_read(uc, UC_TRICORE_REG_PC, &pc)); + TEST_CHECK_(pc == data_start + 0x40, "model=%d pc=0x%08x", + cases[i].model, pc); + OK(uc_reg_read(uc, UC_TRICORE_REG_D15, &value)); + TEST_CHECK_(value == 1, "model=%d tin=%u", cases[i].model, value); + } + + OK(uc_close(uc)); + } +} + TEST_LIST = { {"test_tricore_mov_dreg", test_tricore_mov_dreg}, {"test_tricore_csfr_reg_roundtrip", test_tricore_csfr_reg_roundtrip}, {"test_tricore_cpu_model", test_tricore_cpu_model}, + {"test_tricore_fast_count_boundary", test_tricore_fast_count_boundary}, + {"test_tricore_context_roundtrip", test_tricore_context_roundtrip}, + {"test_tricore_call_ret_csa", test_tricore_call_ret_csa}, + {"test_tricore_svlcx_rslcx_csa", test_tricore_svlcx_rslcx_csa}, + {"test_tricore_csa_exhaustion", test_tricore_csa_exhaustion}, + {"test_tricore_integer_memory_branch", test_tricore_integer_memory_branch}, + {"test_tricore_saturation_and_flags", test_tricore_saturation_and_flags}, + {"test_tricore_circular_addressing", test_tricore_circular_addressing}, + {"test_tricore_bit_reverse_addressing", + test_tricore_bit_reverse_addressing}, + {"test_tricore_fpu_rounding_and_flags", + test_tricore_fpu_rounding_and_flags}, + {"test_tricore_fpu_divide_by_zero_status", + test_tricore_fpu_divide_by_zero_status}, + {"test_tricore_swap_word", test_tricore_swap_word}, + {"test_tricore_cmpswap_word", test_tricore_cmpswap_word}, + {"test_tricore_crc32_model_gating", test_tricore_crc32_model_gating}, {NULL, NULL}, }; diff --git a/tests/unit/test_x86.c b/tests/unit/test_x86.c index ece990980d..f630f1da53 100644 --- a/tests/unit/test_x86.c +++ b/tests/unit/test_x86.c @@ -22,8 +22,21 @@ const uint64_t code_len = 0x4000; #define TEST_X86_CPUID_7_0_EBX_AVX512CD (1U << 28) #define TEST_X86_CPUID_7_0_EBX_AVX512BW (1U << 30) #define TEST_X86_CPUID_7_0_EBX_AVX512VL (1U << 31) +#define TEST_X86_CPUID_1_ECX_XSAVE (1U << 26) +#define TEST_X86_CPUID_7_0_ECX_PKU (1U << 3) #define TEST_X86_CPUID_7_0_ECX_VAES (1U << 9) #define TEST_X86_CPUID_7_0_ECX_VPCLMULQDQ (1U << 10) +#define TEST_X86_CPUID_D_1_EAX_XSAVEOPT (1U << 0) +#define TEST_X86_XSTATE_FP (1ULL << 0) +#define TEST_X86_XSTATE_SSE (1ULL << 1) +#define TEST_X86_XSTATE_YMM (1ULL << 2) +#define TEST_X86_XSTATE_OPMASK (1ULL << 5) +#define TEST_X86_XSTATE_ZMM_HI256 (1ULL << 6) +#define TEST_X86_XSTATE_HI16_ZMM (1ULL << 7) +#define TEST_X86_XSTATE_PKRU (1ULL << 9) +#define TEST_X86_XSAVE_AREA 0x200000ULL +#define TEST_X86_XSAVE_AREA_SIZE 0x2000 +#define TEST_X86_XSAVE_HEADER_OFFSET 512 static void uc_common_setup(uc_engine **uc, uc_arch arch, uc_mode mode, const char *code, uint64_t size) @@ -127,8 +140,8 @@ typedef struct _INSN_IN_RESULT { int size; } INSN_IN_RESULT; -static void test_x86_in_callback(uc_engine *uc, uint32_t port, int size, - void *user_data) +static uint32_t test_x86_in_callback(uc_engine *uc, uint32_t port, int size, + void *user_data) { INSN_IN_RESULT *result = (INSN_IN_RESULT *)user_data; uint32_t eip; @@ -138,6 +151,8 @@ static void test_x86_in_callback(uc_engine *uc, uint32_t port, int size, OK(uc_reg_read(uc, UC_X86_REG_EIP, (void*)&eip)); TEST_CHECK(eip == code_start); + + return 0; } static void test_x86_in(void) @@ -207,9 +222,9 @@ typedef struct _MEM_HOOK_RESULTS { MEM_HOOK_RESULT results[16]; } MEM_HOOK_RESULTS; -static bool test_x86_mem_hook_all_callback(uc_engine *uc, uc_mem_type type, - uint64_t address, int size, - uint64_t value, void *user_data) +static void test_x86_record_mem_hook(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) { MEM_HOOK_RESULTS *r = (MEM_HOOK_RESULTS *)user_data; uint64_t count = r->count; @@ -227,31 +242,52 @@ static bool test_x86_mem_hook_all_callback(uc_engine *uc, uc_mem_type type, if (type == UC_MEM_READ_UNMAPPED) { uc_mem_map(uc, address, 0x1000, UC_PROT_ALL); } +} + +static void test_x86_mem_hook_all_callback(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + test_x86_record_mem_hook(uc, type, address, size, value, user_data); +} +static bool test_x86_invalid_mem_hook_all_callback( + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) +{ + test_x86_record_mem_hook(uc, type, address, size, value, user_data); return true; } static void test_x86_mem_hook_all(void) { uc_engine *uc; - uc_hook hook; + uc_hook invalid_hook; + uc_hook valid_hook; // mov eax, 0xdeadbeef; // mov [0x8000], eax; // mov eax, [0x10000]; char code[] = "\xb8\xef\xbe\xad\xde\xa3\x00\x80\x00\x00\xa1\x00\x00\x01\x00"; MEM_HOOK_RESULTS r = {0}; - MEM_HOOK_RESULT expects[3] = {{UC_MEM_WRITE, 0x8000, 4, 0xdeadbeef}, - {UC_MEM_READ_UNMAPPED, 0x10000, 4, 0}, - {UC_MEM_READ, 0x10000, 4, 0}}; + MEM_HOOK_RESULT expects[] = { + {UC_MEM_FETCH, 0x1000, 5, 0}, + {UC_MEM_FETCH, 0x1005, 5, 0}, + {UC_MEM_WRITE, 0x8000, 4, 0xdeadbeef}, + {UC_MEM_FETCH, 0x100a, 5, 0}, + {UC_MEM_READ_UNMAPPED, 0x10000, 4, 0}, + {UC_MEM_READ, 0x10000, 4, 0}, + }; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_mem_map(uc, 0x8000, 0x1000, UC_PROT_ALL)); - OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_VALID | UC_HOOK_MEM_INVALID, + OK(uc_hook_add(uc, &valid_hook, UC_HOOK_MEM_VALID, test_x86_mem_hook_all_callback, &r, 1, 0)); + OK(uc_hook_add(uc, &invalid_hook, UC_HOOK_MEM_INVALID, + test_x86_invalid_mem_hook_all_callback, &r, 1, 0)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); - TEST_CHECK(r.count == 3); + TEST_ASSERT(r.count == sizeof(expects) / sizeof(expects[0])); for (int i = 0; i < r.count; i++) { TEST_CHECK(expects[i].type == r.results[i].type); TEST_CHECK(expects[i].address == r.results[i].address); @@ -259,6 +295,65 @@ static void test_x86_mem_hook_all(void) TEST_CHECK(expects[i].value == r.results[i].value); } + OK(uc_hook_del(uc, invalid_hook)); + OK(uc_hook_del(uc, valid_hook)); + OK(uc_close(uc)); +} + +typedef struct X86FetchTrace { + uint64_t address; + uint32_t pc; + uint32_t size; + uint32_t count; +} X86FetchTrace; + +static void test_x86_fetch_callback(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + X86FetchTrace *trace = (X86FetchTrace *)user_data; + + TEST_CHECK(type == UC_MEM_FETCH); + TEST_CHECK(value == 0); + trace->count++; + trace->address = address; + trace->size = size; + OK(uc_reg_read(uc, UC_X86_REG_EIP, &trace->pc)); +} + +static void test_x86_invalid_decode_fetch_size(void) +{ + const uint8_t invalid_code[] = {0x0f, 0xff}; + const uint8_t valid_code[] = {0x40}; /* inc eax */ + const uint64_t valid_pc = code_start + 0x10; + X86FetchTrace trace = {0}; + uint32_t eax = 0; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)invalid_code, sizeof(invalid_code)); + OK(uc_mem_write(uc, valid_pc, valid_code, sizeof(valid_code))); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_FETCH, test_x86_fetch_callback, + &trace, 1, 0)); + + uc_assert_err(UC_ERR_INSN_INVALID, + uc_emu_start(uc, code_start, + code_start + sizeof(invalid_code), 0, 1)); + TEST_CHECK(trace.count == 1); + TEST_CHECK(trace.address == code_start); + TEST_CHECK(trace.pc == code_start); + TEST_CHECK(trace.size == sizeof(invalid_code)); + + OK(uc_emu_start(uc, valid_pc, valid_pc + sizeof(valid_code), 0, 1)); + TEST_CHECK(trace.count == 2); + TEST_CHECK(trace.address == valid_pc); + TEST_CHECK(trace.pc == valid_pc); + TEST_CHECK(trace.size == sizeof(valid_code)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 1); + OK(uc_hook_del(uc, hook)); OK(uc_close(uc)); } @@ -903,6 +998,37 @@ static uint32_t test_x86_cpuid_7_0_ecx(uc_cpu_x86 cpu_model) return ecx; } +typedef struct X86CpuidResult { + uint32_t eax; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; +} X86CpuidResult; + +static X86CpuidResult test_x86_cpuid(uc_cpu_x86 cpu_model, uint32_t leaf, + uint32_t subleaf) +{ + const char code[] = "\x0f\xa2"; + X86CpuidResult result = {leaf, 0, subleaf, 0}; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_set_cpu_model(uc, cpu_model)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code) - 1)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &result.eax)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &result.ecx)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &result.eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &result.ebx)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &result.ecx)); + OK(uc_reg_read(uc, UC_X86_REG_EDX, &result.edx)); + + OK(uc_close(uc)); + return result; +} + static uint32_t test_x86_cpuid_7_0_ebx(uc_cpu_x86 cpu_model) { uc_engine *uc; @@ -1252,17 +1378,11 @@ static void test_x86_reg_save(void) OK(uc_close(uc)); } -static bool +static void test_x86_invalid_mem_read_stop_in_cb_callback(uc_engine *uc, uc_mem_type type, uint64_t address, int size, - uint64_t value, void *user_data) + int64_t value, void *user_data) { - // False indicates that we fail to handle this ERROR and let the emulation - // stop. - // - // Note that the memory must be mapped properly if we return true! Check - // test_x86_mem_hook_all for example. - return false; } static void test_x86_invalid_mem_read_stop_in_cb(void) @@ -1386,9 +1506,192 @@ static void test_x86_mmio(void) OK(uc_close(uc)); } +typedef struct X86ReadAfterExitData { + uc_err stop_error; + uint32_t count; + uint32_t value; + bool stop; +} X86ReadAfterExitData; + +static void test_x86_read_after_exit_callback(uc_engine *uc, + uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + X86ReadAfterExitData *data = (X86ReadAfterExitData *)user_data; + + TEST_CHECK(type == UC_MEM_READ_AFTER); + TEST_CHECK(address == 0x200000); + TEST_CHECK(size == 4); + data->count++; + data->value = (uint32_t)value; + if (data->stop) { + data->stop_error = uc_emu_stop(uc); + } +} + +static void test_x86_cputlb_read_after_exit(void) +{ + const uint8_t code[] = { + 0xa1, 0x00, 0x00, 0x20, 0x00, /* mov eax,[0x200000] */ + 0x43, /* inc ebx */ + }; + const uint32_t memory_value = 0x44332211; + const uint32_t initial_eax = 0xdeadbeef; + X86ReadAfterExitData data = {.stop = true}; + uint32_t eax = initial_eax; + uint32_t ebx = 0; + uint32_t eip = 0; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, (const char *)code, + sizeof(code)); + OK(uc_mem_map(uc, 0x200000, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, 0x200000, &memory_value, sizeof(memory_value))); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ_AFTER, + test_x86_read_after_exit_callback, &data, 0x200000, + 0x200003)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(data.stop_error); + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx)); + TEST_CHECK(data.count == 1); + TEST_CHECK(data.value == memory_value); + TEST_CHECK(eip == code_start); + TEST_CHECK(eax == initial_eax); + TEST_CHECK(ebx == 0); + + data.stop = false; + OK(uc_emu_start(uc, eip, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx)); + TEST_CHECK(data.count == 2); + TEST_CHECK(eax == memory_value); + TEST_CHECK(ebx == 1); + + OK(uc_close(uc)); +} + +typedef struct X86MmioExitData { + uc_err stop_error; + uint32_t read_count; + uint32_t write_count; + uint64_t write_value; + bool stop_read; + bool stop_write; +} X86MmioExitData; + +static uint64_t test_x86_mmio_exit_read(uc_engine *uc, uint64_t offset, + unsigned size, void *user_data) +{ + X86MmioExitData *data = (X86MmioExitData *)user_data; + + TEST_CHECK(offset == 0); + TEST_CHECK(size == 4); + data->read_count++; + if (data->stop_read) { + data->stop_error = uc_emu_stop(uc); + } + return 0x66554433; +} + +static void test_x86_mmio_exit_write(uc_engine *uc, uint64_t offset, + unsigned size, uint64_t value, + void *user_data) +{ + X86MmioExitData *data = (X86MmioExitData *)user_data; + + TEST_CHECK(offset == 4); + TEST_CHECK(size == 4); + data->write_count++; + data->write_value = value; + if (data->stop_write) { + data->stop_error = uc_emu_stop(uc); + } +} + +static void test_x86_cputlb_split_mmio_exit(void) +{ + const uint64_t test_code = 0x100000; + const uint8_t read_code[] = { + 0xa1, 0xfe, 0x2f, 0x00, 0x00, /* mov eax,[0x2ffe] */ + 0x43, /* inc ebx */ + }; + const uint8_t write_code[] = { + 0xa3, 0x04, 0x30, 0x00, 0x00, /* mov [0x3004],eax */ + 0x41, /* inc ecx */ + }; + const uint8_t ram_tail[] = {0x11, 0x22}; + const uint32_t initial_eax = 0xdeadbeef; + X86MmioExitData data = {.stop_read = true}; + uint32_t eax = initial_eax; + uint32_t ebx = 0; + uint32_t ecx = 0; + uint32_t eip = 0; + uc_engine *uc; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, test_code, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, test_code, read_code, sizeof(read_code))); + OK(uc_mem_write(uc, test_code + 0x100, write_code, sizeof(write_code))); + OK(uc_mem_map(uc, 0x2000, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, 0x2ffe, ram_tail, sizeof(ram_tail))); + OK(uc_mmio_map(uc, 0x3000, 0x1000, test_x86_mmio_exit_read, &data, + test_x86_mmio_exit_write, &data)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + + OK(uc_emu_start(uc, test_code, test_code + sizeof(read_code), 0, 0)); + OK(data.stop_error); + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx)); + TEST_CHECK(data.read_count == 1); + TEST_CHECK(eip == test_code); + TEST_CHECK(eax == initial_eax); + TEST_CHECK(ebx == 0); + + data.stop_read = false; + OK(uc_emu_start(uc, eip, test_code + sizeof(read_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx)); + TEST_CHECK(data.read_count == 2); + TEST_CHECK(eax == 0x44332211); + TEST_CHECK(ebx == 1); + + eax = 0x87654321; + data.stop_write = true; + data.stop_error = UC_ERR_OK; + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &ecx)); + OK(uc_emu_start(uc, test_code + 0x100, + test_code + 0x100 + sizeof(write_code), 0, 0)); + OK(data.stop_error); + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + TEST_CHECK(data.write_count == 1); + TEST_CHECK(data.write_value == eax); + TEST_CHECK(eip == test_code + 0x100); + TEST_CHECK(ecx == 0); + + data.stop_write = false; + OK(uc_emu_start(uc, eip, test_code + 0x100 + sizeof(write_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + TEST_CHECK(data.write_count == 2); + TEST_CHECK(data.write_value == eax); + TEST_CHECK(ecx == 1); + + OK(uc_close(uc)); +} + static bool test_x86_missing_code_callback(uc_engine *uc, uc_mem_type type, uint64_t address, int size, - uint64_t value, void *user_data) + int64_t value, void *user_data) { char code[] = "\x41\x4a"; // inc ecx; dec edx; uint64_t algined_address = address & 0xFFFFFFFFFFFFF000ULL; @@ -1476,7 +1779,7 @@ static void test_x86_smc_add(void) static void test_x86_smc_mem_hook_callback(uc_engine *uc, uc_mem_type t, uint64_t addr, int size, - uint64_t value, void *user_data) + int64_t value, void *user_data) { uint64_t write_addresses[] = {0x1030, 0x1010, 0x1010, 0x1018, 0x1018, 0x1029, 0x1029}; @@ -1846,6 +2149,94 @@ static void test_x86_clear_count_cache(void) OK(uc_close(uc)); } +static void test_x86_large_instruction_count(void) +{ + const uint8_t code[] = { + 0x40, /* inc eax */ + 0xeb, 0xfd, /* jmp loop */ + }; + const size_t counts[] = { 1, 2, 3, 65535, 65536, 70000 }; + uc_engine *uc; + size_t i; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, (const char *)code, + sizeof(code)); + for (i = 0; i < sizeof(counts) / sizeof(counts[0]); i++) { + uint32_t eax = 0; + uint32_t eip; + + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_emu_start(uc, code_start, 0, 0, counts[i])); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + TEST_CHECK_(eax == (counts[i] + 1) / 2, + "count=%zu eax=%u", counts[i], eax); + TEST_CHECK_(eip == code_start + (counts[i] & 1), + "count=%zu eip=0x%x", counts[i], eip); + } + + OK(uc_close(uc)); +} + +typedef struct X86CountPcChangeData { + uint32_t count; + uint32_t destination; +} X86CountPcChangeData; + +static void test_x86_count_pc_change_callback(uc_engine *uc, + uint64_t address, + uint32_t size, + void *user_data) +{ + X86CountPcChangeData *data = + (X86CountPcChangeData *)user_data; + + data->count++; + OK(uc_reg_write(uc, UC_X86_REG_EIP, &data->destination)); +} + +static void test_x86_count_nonmatching_callback(uc_engine *uc, + uint64_t address, + uint32_t size, + void *user_data) +{ + TEST_CHECK(false); +} + +static void test_x86_instruction_count_pc_change_refund(void) +{ + const uint64_t destination = code_start + 0x1000; + const uint8_t code[] = { 0x40, 0x40, 0x40, 0x40 }; /* inc eax */ + X86CountPcChangeData data = { + .destination = (uint32_t)destination, + }; + uint32_t eax = 0; + uint32_t eip; + uc_engine *uc; + uc_hook hook; + uc_hook nonmatching_hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, (const char *)code, + sizeof(code)); + OK(uc_mem_write(uc, destination, code, sizeof(code))); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_hook_add(uc, &nonmatching_hook, UC_HOOK_CODE, + test_x86_count_nonmatching_callback, NULL, + code_start + 0x100, code_start + 0x100)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, + test_x86_count_pc_change_callback, &data, + code_start + 1, code_start + 1)); + + OK(uc_emu_start(uc, code_start, 0, 0, 3)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + TEST_CHECK_(eax == 3, "eax=%u", eax); + TEST_CHECK_(eip == destination + 2, "eip=0x%x", eip); + TEST_CHECK(data.count == 1); + + OK(uc_close(uc)); +} + // This is a regression bug. static void test_x86_clear_empty_tb(void) { @@ -2063,9 +2454,9 @@ static void test_x86_hook_tcg_op(void) OK(uc_close(uc)); } -static bool test_x86_cmpxchg_mem_hook(uc_engine *uc, uc_mem_type type, - uint64_t address, int size, int64_t val, - void *data) +static void test_x86_cmpxchg_mem_hook(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t val, + void *data) { if (type == UC_MEM_READ) { *((int *)data) |= 1; @@ -2073,7 +2464,6 @@ static bool test_x86_cmpxchg_mem_hook(uc_engine *uc, uc_mem_type type, *((int *)data) |= 2; } - return true; } static void test_x86_cmpxchg(void) @@ -2321,7 +2711,7 @@ static void test_x86_pext32_zero_extend(void) } static void test_x86_nested_emu_start_cb(uc_engine *uc, uint64_t addr, - size_t size, void *data) + uint32_t size, void *data) { OK(uc_emu_start(uc, code_start + 1, code_start + 2, 0, 0)); } @@ -2353,8 +2743,54 @@ static void test_x86_nested_emu_start(void) OK(uc_close(uc)); } +typedef struct X86NestedCountData { + uint64_t inner_address; + uint32_t count; +} X86NestedCountData; + +static void test_x86_nested_count_callback(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + X86NestedCountData *data = (X86NestedCountData *)user_data; + + data->count++; + OK(uc_emu_start(uc, data->inner_address, data->inner_address + 1, + 0, 1)); +} + +static void test_x86_nested_count_state(void) +{ + const uint64_t inner_address = code_start + 0x1000; + const uint8_t outer_code[] = { 0x40, 0x40, 0x40 }; /* inc eax */ + const uint8_t inner_code[] = { 0x4b }; /* dec ebx */ + X86NestedCountData data = { .inner_address = inner_address }; + uint32_t eax = 0; + uint32_t ebx = 10; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)outer_code, sizeof(outer_code)); + OK(uc_mem_write(uc, inner_address, inner_code, sizeof(inner_code))); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, + test_x86_nested_count_callback, &data, + code_start, code_start)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(outer_code), + 0, 2)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx)); + TEST_CHECK(eax == 2); + TEST_CHECK(ebx == 9); + TEST_CHECK(data.count == 1); + + OK(uc_close(uc)); +} + static void test_x86_nested_emu_stop_cb(uc_engine *uc, uint64_t addr, - size_t size, void *data) + uint32_t size, void *data) { OK(uc_emu_start(uc, code_start + 1, code_start + 2, 0, 0)); // ecx shouldn't be changed! @@ -2389,7 +2825,7 @@ static void test_x86_nested_emu_stop(void) } static void test_x86_nested_emu_start_error_cb(uc_engine *uc, uint64_t addr, - size_t size, void *data) + uint32_t size, void *data) { uc_assert_err(UC_ERR_READ_UNMAPPED, uc_emu_start(uc, code_start + 2, 0, 0, 0)); @@ -2412,6 +2848,65 @@ static void test_x86_64_nested_emu_start_error(void) OK(uc_close(uc)); } +typedef struct X86NestedDepthData { + uint32_t callbacks; + uint32_t successful_starts; + uint32_t returns; + uint32_t resource_errors; +} X86NestedDepthData; + +static void test_x86_nested_emu_start_max_depth_cb(uc_engine *uc, + uint64_t address, + uint32_t size, + void *user_data) +{ + X86NestedDepthData *data = (X86NestedDepthData *)user_data; + uc_err err; + + data->callbacks++; + err = uc_emu_start(uc, code_start, code_start + 1, 0, 0); + if (err == UC_ERR_RESOURCE) { + data->resource_errors++; + OK(uc_ctl_remove_cache(uc, code_start, code_start + 1)); + } else { + uc_assert_err(UC_ERR_OK, err); + if (err == UC_ERR_OK) { + data->successful_starts++; + } + } + data->returns++; +} + +static void test_x86_nested_emu_start_max_depth(void) +{ + const uint8_t nop[] = { 0x90 }; + const uint8_t inc_eax[] = { 0x40 }; + X86NestedDepthData data = { 0 }; + uint32_t eax = 0; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)nop, sizeof(nop)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, + test_x86_nested_emu_start_max_depth_cb, &data, + code_start, code_start)); + + OK(uc_emu_start(uc, code_start, code_start + 1, 0, 0)); + TEST_CHECK(data.resource_errors == 1); + TEST_CHECK(data.callbacks == data.returns); + TEST_CHECK(data.successful_starts + 1 == data.callbacks); + + OK(uc_hook_del(uc, hook)); + OK(uc_mem_write(uc, code_start, inc_eax, sizeof(inc_eax))); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_emu_start(uc, code_start, code_start + 1, 0, 1)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 1); + + OK(uc_close(uc)); +} + static void test_x86_eflags_reserved_bit(void) { uc_engine *uc; @@ -2550,7 +3045,7 @@ static void test_x86_bzhi_index_boundary(void) } static void test_x86_nested_uc_emu_start_exits_cb(uc_engine *uc, uint64_t addr, - size_t size, void *data) + uint32_t size, void *data) { OK(uc_emu_start(uc, code_start + 5, code_start + 6, 0, 0)); } @@ -2579,8 +3074,8 @@ static void test_x86_nested_uc_emu_start_exits(void) } static bool test_x86_correct_address_in_small_jump_hook_callback( - uc_engine *uc, int type, uint64_t address, int size, int64_t value, - void *user_data) + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) { // Check registers uint64_t r_rax = 0x0; @@ -2626,8 +3121,8 @@ static void test_x86_correct_address_in_small_jump_hook(void) } static bool test_x86_correct_address_in_long_jump_hook_callback( - uc_engine *uc, int type, uint64_t address, int size, int64_t value, - void *user_data) + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) { // Check registers uint64_t r_rax = 0x0; @@ -2708,10 +3203,11 @@ static void test_x86_intr_capture_cb(uc_engine *uc, uint32_t intno, void *data) static void test_x86_sse_aligned_access(void) { - const uint64_t data_addr = 0x200008; + const uint64_t aligned_stack = 0x200000; + const uint64_t unaligned_stack = aligned_stack + 8; const uint8_t code[] = { - 0x0f, 0x11, 0x00, /* movups [rax], xmm0 */ - 0x0f, 0x29, 0x00, /* movaps [rax], xmm0 */ + 0x0f, 0x11, 0x04, 0x24, /* movups [rsp], xmm0 */ + 0x0f, 0x29, 0x04, 0x24, /* movaps [rsp], xmm0 */ }; const uint8_t xmm0[16] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, @@ -2725,67 +3221,526 @@ static void test_x86_sse_aligned_access(void) X86IntrCapture capture = { 0 }; uc_engine *uc; uc_hook hook; - uint64_t rax = data_addr; + uint64_t rip; + uint64_t rsp = unaligned_stack; OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); OK(uc_ctl_set_cpu_model(uc, UC_CPU_X86_HASWELL)); OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); OK(uc_mem_write(uc, code_start, code, sizeof(code))); - OK(uc_mem_map(uc, 0x200000, 0x1000, UC_PROT_ALL)); - OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_x86_intr_capture_cb, - &capture, 1, 0)); - OK(uc_reg_write(uc, UC_X86_REG_RAX, &rax)); + OK(uc_mem_map(uc, aligned_stack, 0x1000, UC_PROT_ALL)); + OK(uc_reg_write(uc, UC_X86_REG_RSP, &rsp)); OK(uc_reg_write(uc, UC_X86_REG_XMM0, &xmm0)); - OK(uc_emu_start(uc, code_start, code_start + 3, 0, 1)); - OK(uc_mem_read(uc, data_addr, memory, sizeof(memory))); - TEST_CHECK(capture.count == 0); + OK(uc_emu_start(uc, code_start, code_start + 4, 0, 1)); + OK(uc_mem_read(uc, unaligned_stack, memory, sizeof(memory))); TEST_CHECK(memcmp(memory, xmm0, sizeof(memory)) == 0); - OK(uc_mem_write(uc, data_addr, sentinel, sizeof(sentinel))); - OK(uc_emu_start(uc, code_start + 3, code_start + sizeof(code), 0, 1)); - OK(uc_mem_read(uc, data_addr, memory, sizeof(memory))); + OK(uc_mem_write(uc, unaligned_stack, sentinel, sizeof(sentinel))); + OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_x86_intr_capture_cb, + &capture, 1, 0)); + OK(uc_emu_start(uc, code_start + 4, code_start + sizeof(code), 0, 1)); + OK(uc_reg_read(uc, UC_X86_REG_RIP, &rip)); + OK(uc_mem_read(uc, unaligned_stack, memory, sizeof(memory))); + TEST_CHECK(rip == code_start + 4); TEST_CHECK(capture.count == 1); - TEST_CHECK(capture.intno == 13); + TEST_CHECK_(capture.intno == 13, "intno=%u", capture.intno); TEST_CHECK(memcmp(memory, sentinel, sizeof(memory)) == 0); + rsp = aligned_stack; + OK(uc_reg_write(uc, UC_X86_REG_RSP, &rsp)); + OK(uc_mem_write(uc, aligned_stack, sentinel, sizeof(sentinel))); + OK(uc_emu_start(uc, code_start + 4, code_start + sizeof(code), 0, 1)); + OK(uc_mem_read(uc, aligned_stack, memory, sizeof(memory))); + TEST_CHECK(capture.count == 1); + TEST_CHECK(memcmp(memory, xmm0, sizeof(memory)) == 0); + OK(uc_close(uc)); } -static void test_x86_data_watchpoint(void) +static void test_x86_movdqa_movdqu_alignment(void) { - const uint64_t data_addr = 0x200000; + const uint64_t address = 0x200008; const uint8_t code[] = { - 0xc7, 0x00, 0x44, 0x33, 0x22, 0x11, /* mov dword ptr [rax], 0x11223344 */ + 0xf3, 0x0f, 0x7f, 0x04, 0x24, /* movdqu [rsp], xmm0 */ + 0x66, 0x0f, 0x7f, 0x04, 0x24, /* movdqa [rsp], xmm0 */ }; - const uint64_t dr7_write_len4 = 1 | (1U << 16) | (3U << 18); - X86IntrCapture capture = { 0 }; - uint32_t memory = 0; - uint64_t dr6 = 0; - uint64_t rax = data_addr; + const uint8_t xmm0[16] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + }; + const uint8_t sentinel[16] = { + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + }; + uint8_t memory[16]; uc_engine *uc; - uc_hook hook; + uint64_t rip; + uint64_t rsp = address; - uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, (const char *)code, - sizeof(code)); - OK(uc_mem_map(uc, data_addr, 0x1000, UC_PROT_ALL)); - OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_x86_intr_capture_cb, - &capture, 1, 0)); - OK(uc_reg_write(uc, UC_X86_REG_RAX, &rax)); - OK(uc_reg_write(uc, UC_X86_REG_DR0, &rax)); - OK(uc_reg_write(uc, UC_X86_REG_DR7, &dr7_write_len4)); + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_X86_HASWELL)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_mem_map(uc, address & ~0xfffULL, 0x1000, UC_PROT_ALL)); + OK(uc_reg_write(uc, UC_X86_REG_RSP, &rsp)); + OK(uc_reg_write(uc, UC_X86_REG_XMM0, &xmm0)); - OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); - OK(uc_mem_read(uc, data_addr, &memory, sizeof(memory))); - OK(uc_reg_read(uc, UC_X86_REG_DR6, &dr6)); - TEST_CHECK(capture.count == 1); - TEST_CHECK(capture.intno == 1); - TEST_CHECK(memory == 0x11223344); - TEST_CHECK((dr6 & 1) != 0); + OK(uc_emu_start(uc, code_start, code_start + 5, 0, 1)); + OK(uc_mem_read(uc, address, memory, sizeof(memory))); + TEST_CHECK(memcmp(memory, xmm0, sizeof(memory)) == 0); + + OK(uc_mem_write(uc, address, sentinel, sizeof(sentinel))); + uc_assert_err(UC_ERR_EXCEPTION, + uc_emu_start(uc, code_start + 5, + code_start + sizeof(code), 0, 1)); + OK(uc_reg_read(uc, UC_X86_REG_RIP, &rip)); + OK(uc_mem_read(uc, address, memory, sizeof(memory))); + TEST_CHECK(rip == code_start + 5); + TEST_CHECK(memcmp(memory, sentinel, sizeof(memory)) == 0); + + OK(uc_close(uc)); +} + +static void test_x86_xsave_setup(uc_engine **uc, uc_cpu_x86 cpu_model, + const uint8_t *code, size_t code_size) +{ + const uint8_t empty_header[64] = {0}; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, uc)); + OK(uc_ctl_set_cpu_model(*uc, cpu_model)); + OK(uc_mem_map(*uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(*uc, code_start, code, code_size)); + OK(uc_mem_map(*uc, TEST_X86_XSAVE_AREA, TEST_X86_XSAVE_AREA_SIZE, + UC_PROT_ALL)); + OK(uc_mem_write(*uc, TEST_X86_XSAVE_AREA + TEST_X86_XSAVE_HEADER_OFFSET, + empty_header, sizeof(empty_header))); +} + +static uc_err test_x86_run_xsave_instruction(uc_engine *uc, uint64_t pc, + size_t insn_size, uint64_t area, + uint64_t mask) +{ + uint32_t eax = (uint32_t)mask; + uint32_t edx = (uint32_t)(mask >> 32); + + OK(uc_reg_write(uc, UC_X86_REG_RDI, &area)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EDX, &edx)); + return uc_emu_start(uc, pc, pc + insn_size, 0, 1); +} + +static void test_x86_xsave_xrstor_roundtrip(void) +{ + const uint8_t code[] = { + 0x0f, 0xae, 0x27, /* xsave [rdi] */ + 0x0f, 0xae, 0x2f, /* xrstor [rdi] */ + }; + const uint8_t initial_st0[10] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x3f, + }; + const uint8_t changed_st0[10] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x40, + }; + const uint64_t initial_ymm0[4] = { + 0x0011223344556677ULL, + 0x8899aabbccddeeffULL, + 0x1021324354657687ULL, + 0x98a9bacbdcedfe0fULL, + }; + const uint64_t changed_ymm0[4] = { + 0xffffffffffffffffULL, + 0xeeeeeeeeeeeeeeeeULL, + 0xddddddddddddddddULL, + 0xccccccccccccccccULL, + }; + const uint64_t mask = + TEST_X86_XSTATE_FP | TEST_X86_XSTATE_SSE | TEST_X86_XSTATE_YMM; + uint8_t st0[sizeof(initial_st0)]; + uint64_t ymm0[4]; + uint64_t xstate_bv; + uint16_t fpcw = 0x037f; + uint32_t mxcsr = 0x1f80; + uc_engine *uc; + + test_x86_xsave_setup(&uc, UC_CPU_X86_HASWELL, code, sizeof(code)); + OK(uc_reg_write(uc, UC_X86_REG_ST0, initial_st0)); + OK(uc_reg_write(uc, UC_X86_REG_FPCW, &fpcw)); + OK(uc_reg_write(uc, UC_X86_REG_MXCSR, &mxcsr)); + OK(uc_reg_write(uc, UC_X86_REG_YMM0, initial_ymm0)); + + OK(test_x86_run_xsave_instruction(uc, code_start, 3, TEST_X86_XSAVE_AREA, + mask)); + OK(uc_mem_read(uc, TEST_X86_XSAVE_AREA + TEST_X86_XSAVE_HEADER_OFFSET, + &xstate_bv, sizeof(xstate_bv))); + TEST_CHECK((xstate_bv & mask) == mask); + + fpcw = 0x077f; + mxcsr = 0x3f80; + OK(uc_reg_write(uc, UC_X86_REG_ST0, changed_st0)); + OK(uc_reg_write(uc, UC_X86_REG_FPCW, &fpcw)); + OK(uc_reg_write(uc, UC_X86_REG_MXCSR, &mxcsr)); + OK(uc_reg_write(uc, UC_X86_REG_YMM0, changed_ymm0)); + + OK(test_x86_run_xsave_instruction(uc, code_start + 3, 3, + TEST_X86_XSAVE_AREA, mask)); + OK(uc_reg_read(uc, UC_X86_REG_ST0, st0)); + OK(uc_reg_read(uc, UC_X86_REG_FPCW, &fpcw)); + OK(uc_reg_read(uc, UC_X86_REG_MXCSR, &mxcsr)); + OK(uc_reg_read(uc, UC_X86_REG_YMM0, ymm0)); + + TEST_CHECK(memcmp(st0, initial_st0, sizeof(st0)) == 0); + TEST_CHECK(fpcw == 0x037f); + TEST_CHECK(mxcsr == 0x1f80); + TEST_CHECK(memcmp(ymm0, initial_ymm0, sizeof(ymm0)) == 0); + + OK(uc_close(uc)); +} + +static void test_x86_xsaveopt_xrstor_roundtrip(void) +{ + const uint8_t code[] = { + 0x0f, 0xae, 0x37, /* xsaveopt [rdi] */ + 0x0f, 0xae, 0x2f, /* xrstor [rdi] */ + }; + const uint64_t initial_ymm2[4] = { + 0x0123456789abcdefULL, + 0xfedcba9876543210ULL, + 0x0f1e2d3c4b5a6978ULL, + 0x8796a5b4c3d2e1f0ULL, + }; + const uint64_t changed_ymm2[4] = { + 0x1111111111111111ULL, + 0x2222222222222222ULL, + 0x3333333333333333ULL, + 0x4444444444444444ULL, + }; + const uint64_t mask = + TEST_X86_XSTATE_FP | TEST_X86_XSTATE_SSE | TEST_X86_XSTATE_YMM; + X86CpuidResult cpuid; + uint64_t ymm2[4]; + uc_engine *uc; + + cpuid = test_x86_cpuid(UC_CPU_X86_HASWELL, 0xd, 1); + TEST_CHECK((cpuid.eax & TEST_X86_CPUID_D_1_EAX_XSAVEOPT) != 0); + + test_x86_xsave_setup(&uc, UC_CPU_X86_HASWELL, code, sizeof(code)); + OK(uc_reg_write(uc, UC_X86_REG_YMM2, initial_ymm2)); + OK(test_x86_run_xsave_instruction(uc, code_start, 3, TEST_X86_XSAVE_AREA, + mask)); + OK(uc_reg_write(uc, UC_X86_REG_YMM2, changed_ymm2)); + OK(test_x86_run_xsave_instruction(uc, code_start + 3, 3, + TEST_X86_XSAVE_AREA, mask)); + OK(uc_reg_read(uc, UC_X86_REG_YMM2, ymm2)); + TEST_CHECK(memcmp(ymm2, initial_ymm2, sizeof(ymm2)) == 0); + + OK(uc_close(uc)); +} + +static void test_x86_xsave_xcr0_mask(void) +{ + const uint8_t code[] = { + 0x0f, 0xae, 0x27, /* xsave [rdi] */ + 0x0f, 0xae, 0x2f, /* xrstor [rdi] */ + }; + const uint64_t initial_ymm0[4] = { + 0x0011223344556677ULL, + 0x8899aabbccddeeffULL, + 0x1021324354657687ULL, + 0x98a9bacbdcedfe0fULL, + }; + const uint64_t changed_ymm0[4] = { + 0x1111111111111111ULL, + 0x2222222222222222ULL, + 0x3333333333333333ULL, + 0x4444444444444444ULL, + }; + const uint64_t expected_ymm0[4] = { + 0x0011223344556677ULL, + 0x8899aabbccddeeffULL, + 0x3333333333333333ULL, + 0x4444444444444444ULL, + }; + const uint64_t requested_mask = + TEST_X86_XSTATE_FP | TEST_X86_XSTATE_SSE | TEST_X86_XSTATE_YMM; + const uint64_t xcr0 = TEST_X86_XSTATE_FP | TEST_X86_XSTATE_SSE; + uint64_t xstate_bv; + uint64_t ymm0[4]; + uc_engine *uc; + + test_x86_xsave_setup(&uc, UC_CPU_X86_HASWELL, code, sizeof(code)); + OK(uc_reg_write(uc, UC_X86_REG_XCR0, &xcr0)); + OK(uc_reg_write(uc, UC_X86_REG_YMM0, initial_ymm0)); + OK(test_x86_run_xsave_instruction(uc, code_start, 3, TEST_X86_XSAVE_AREA, + requested_mask)); + OK(uc_mem_read(uc, TEST_X86_XSAVE_AREA + TEST_X86_XSAVE_HEADER_OFFSET, + &xstate_bv, sizeof(xstate_bv))); + TEST_CHECK((xstate_bv & requested_mask) == xcr0); + + OK(uc_reg_write(uc, UC_X86_REG_YMM0, changed_ymm0)); + OK(test_x86_run_xsave_instruction(uc, code_start + 3, 3, + TEST_X86_XSAVE_AREA, requested_mask)); + OK(uc_reg_read(uc, UC_X86_REG_YMM0, ymm0)); + TEST_CHECK(memcmp(ymm0, expected_ymm0, sizeof(ymm0)) == 0); + + OK(uc_close(uc)); +} + +static void test_x86_xsave_model_gating(void) +{ + const uint8_t code[] = { + 0x0f, 0xae, 0x27, /* xsave [rdi] */ + 0x0f, 0xae, 0x37, /* xsaveopt [rdi] */ + }; + const uint64_t avx512_xstate = TEST_X86_XSTATE_OPMASK | + TEST_X86_XSTATE_ZMM_HI256 | + TEST_X86_XSTATE_HI16_ZMM; + X86CpuidResult cpuid; + uint64_t supported_xstate; + uc_engine *uc; + + cpuid = test_x86_cpuid(UC_CPU_X86_PENRYN, 1, 0); + TEST_CHECK((cpuid.ecx & TEST_X86_CPUID_1_ECX_XSAVE) == 0); + cpuid = test_x86_cpuid(UC_CPU_X86_PENRYN, 0xd, 0); + TEST_CHECK(cpuid.eax == 0); + TEST_CHECK(cpuid.ebx == 0); + TEST_CHECK(cpuid.ecx == 0); + TEST_CHECK(cpuid.edx == 0); + + test_x86_xsave_setup(&uc, UC_CPU_X86_PENRYN, code, sizeof(code)); + uc_assert_err(UC_ERR_INSN_INVALID, + test_x86_run_xsave_instruction(uc, code_start, 3, + TEST_X86_XSAVE_AREA, + TEST_X86_XSTATE_FP)); + OK(uc_close(uc)); + + cpuid = test_x86_cpuid(UC_CPU_X86_OPTERON_G4, 1, 0); + TEST_CHECK((cpuid.ecx & TEST_X86_CPUID_1_ECX_XSAVE) != 0); + cpuid = test_x86_cpuid(UC_CPU_X86_OPTERON_G4, 0xd, 1); + TEST_CHECK((cpuid.eax & TEST_X86_CPUID_D_1_EAX_XSAVEOPT) == 0); + + test_x86_xsave_setup(&uc, UC_CPU_X86_OPTERON_G4, code, sizeof(code)); + uc_assert_err(UC_ERR_INSN_INVALID, + test_x86_run_xsave_instruction(uc, code_start + 3, 3, + TEST_X86_XSAVE_AREA, + TEST_X86_XSTATE_FP)); + OK(uc_close(uc)); + + cpuid = test_x86_cpuid(UC_CPU_X86_ICELAKE_CLIENT, 0xd, 0); + supported_xstate = cpuid.eax | ((uint64_t)cpuid.edx << 32); + /* TCG filters AVX-512, so guest XSAVE must not expose its state. */ + TEST_CHECK((supported_xstate & avx512_xstate) == 0); +} + +static void test_x86_xsave_alignment_fault(void) +{ + const uint8_t code[] = { + 0x0f, 0xae, 0x27, /* xsave [rdi] */ + 0x0f, 0xae, 0x2f, /* xrstor [rdi] */ + }; + const uint8_t sentinel[32] = { + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + }; + const uint64_t mask = + TEST_X86_XSTATE_FP | TEST_X86_XSTATE_SSE | TEST_X86_XSTATE_YMM; + X86IntrCapture capture = {0}; + uint8_t memory[sizeof(sentinel)]; + uc_engine *uc; + uc_hook hook; + + test_x86_xsave_setup(&uc, UC_CPU_X86_HASWELL, code, sizeof(code)); + OK(uc_mem_write(uc, TEST_X86_XSAVE_AREA, sentinel, sizeof(sentinel))); + OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_x86_intr_capture_cb, &capture, + 1, 0)); + + OK(test_x86_run_xsave_instruction(uc, code_start, 3, + TEST_X86_XSAVE_AREA + 1, mask)); + TEST_CHECK(capture.count == 1); + TEST_CHECK(capture.intno == 13); + OK(uc_mem_read(uc, TEST_X86_XSAVE_AREA, memory, sizeof(memory))); + TEST_CHECK(memcmp(memory, sentinel, sizeof(memory)) == 0); + OK(uc_close(uc)); + + capture.count = 0; + capture.intno = 0; + test_x86_xsave_setup(&uc, UC_CPU_X86_HASWELL, code, sizeof(code)); + OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_x86_intr_capture_cb, &capture, + 1, 0)); + OK(test_x86_run_xsave_instruction(uc, code_start + 3, 3, + TEST_X86_XSAVE_AREA + 1, mask)); + TEST_CHECK(capture.count == 1); + TEST_CHECK(capture.intno == 13); + + OK(uc_close(uc)); +} + +static void test_x86_xsave_pkru_roundtrip(void) +{ + const uint8_t code[] = { + 0x0f, 0x01, 0xef, /* wrpkru */ + 0x0f, 0xae, 0x27, /* xsave [rdi] */ + 0x0f, 0xae, 0x2f, /* xrstor [rdi] */ + 0x0f, 0x01, 0xee, /* rdpkru */ + }; + const uint32_t initial_pkru = 0x5a5aa5a5; + const uint32_t changed_pkru = 0xa5a55a5a; + X86CpuidResult cpuid; + X86CpuidResult pkru_leaf; + uint64_t xcr0; + uint64_t cr4; + uint64_t xstate_bv; + uint32_t eax; + uint32_t ecx = 0; + uint32_t edx = 0; + uint32_t saved_pkru; + uc_engine *uc; + + cpuid = test_x86_cpuid(UC_CPU_X86_HASWELL, 7, 0); + TEST_CHECK((cpuid.ecx & TEST_X86_CPUID_7_0_ECX_PKU) == 0); + cpuid = test_x86_cpuid(UC_CPU_X86_ICELAKE_CLIENT, 7, 0); + TEST_CHECK((cpuid.ecx & TEST_X86_CPUID_7_0_ECX_PKU) != 0); + cpuid = test_x86_cpuid(UC_CPU_X86_ICELAKE_CLIENT, 0xd, 0); + TEST_CHECK((cpuid.eax & TEST_X86_XSTATE_PKRU) != 0); + pkru_leaf = test_x86_cpuid(UC_CPU_X86_ICELAKE_CLIENT, 0xd, 9); + TEST_ASSERT(pkru_leaf.eax == sizeof(saved_pkru) * 2); + TEST_ASSERT(pkru_leaf.ebx + pkru_leaf.eax <= TEST_X86_XSAVE_AREA_SIZE); + + test_x86_xsave_setup(&uc, UC_CPU_X86_ICELAKE_CLIENT, code, sizeof(code)); + OK(uc_reg_read(uc, UC_X86_REG_XCR0, &xcr0)); + TEST_CHECK((xcr0 & TEST_X86_XSTATE_PKRU) != 0); + OK(uc_reg_read(uc, UC_X86_REG_CR4, &cr4)); + cr4 |= 1ULL << 22; + OK(uc_reg_write(uc, UC_X86_REG_CR4, &cr4)); + + eax = initial_pkru; + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &ecx)); + OK(uc_reg_write(uc, UC_X86_REG_EDX, &edx)); + OK(uc_emu_start(uc, code_start, code_start + 3, 0, 1)); + + OK(test_x86_run_xsave_instruction( + uc, code_start + 3, 3, TEST_X86_XSAVE_AREA, TEST_X86_XSTATE_PKRU)); + OK(uc_mem_read(uc, TEST_X86_XSAVE_AREA + pkru_leaf.ebx, &saved_pkru, + sizeof(saved_pkru))); + OK(uc_mem_read(uc, TEST_X86_XSAVE_AREA + TEST_X86_XSAVE_HEADER_OFFSET, + &xstate_bv, sizeof(xstate_bv))); + TEST_CHECK(saved_pkru == initial_pkru); + TEST_CHECK((xstate_bv & TEST_X86_XSTATE_PKRU) != 0); + + eax = changed_pkru; + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &ecx)); + OK(uc_reg_write(uc, UC_X86_REG_EDX, &edx)); + OK(uc_emu_start(uc, code_start, code_start + 3, 0, 1)); + + OK(test_x86_run_xsave_instruction( + uc, code_start + 6, 3, TEST_X86_XSAVE_AREA, TEST_X86_XSTATE_PKRU)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &ecx)); + OK(uc_emu_start(uc, code_start + 9, code_start + 12, 0, 1)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == initial_pkru); + + OK(uc_close(uc)); +} + +static void test_x86_data_watchpoint(void) +{ + const uint64_t data_addr = 0x200000; + const uint8_t code[] = { + 0xc7, 0x00, 0x44, 0x33, 0x22, 0x11, /* mov dword ptr [rax], 0x11223344 */ + }; + const uint64_t dr7_write_len4 = 1 | (1U << 16) | (3U << 18); + X86IntrCapture capture = { 0 }; + uint32_t memory = 0; + uint64_t dr6 = 0; + uint64_t rax = data_addr; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, (const char *)code, + sizeof(code)); + OK(uc_mem_map(uc, data_addr, 0x1000, UC_PROT_ALL)); + OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_x86_intr_capture_cb, + &capture, 1, 0)); + OK(uc_reg_write(uc, UC_X86_REG_RAX, &rax)); + OK(uc_reg_write(uc, UC_X86_REG_DR0, &rax)); + OK(uc_reg_write(uc, UC_X86_REG_DR7, &dr7_write_len4)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_mem_read(uc, data_addr, &memory, sizeof(memory))); + OK(uc_reg_read(uc, UC_X86_REG_DR6, &dr6)); + TEST_CHECK(capture.count == 1); + TEST_CHECK(capture.intno == 1); + TEST_CHECK(memory == 0x11223344); + TEST_CHECK((dr6 & 1) != 0); OK(uc_close(uc)); } +static void test_x86_context_check_debug_state(uc_engine *uc, + X86IntrCapture *capture) +{ + uc_hook hook; + + OK(uc_hook_add(uc, &hook, UC_HOOK_INTR, test_x86_intr_capture_cb, + capture, 1, 0)); + + OK(uc_emu_start(uc, code_start, code_start + 1, 0, 1)); + TEST_CHECK_(capture->count == 1, + "breakpoint count=%u intno=%u", capture->count, + capture->intno); + + capture->count = 0; + capture->intno = 0; + OK(uc_emu_start(uc, code_start + 1, code_start + 7, 0, 1)); + TEST_CHECK_(capture->count == 1, + "watchpoint count=%u intno=%u", capture->count, + capture->intno); +} + +static void test_x86_context_debug_lifecycle(void) +{ + const uint64_t data_address = 0x200000; + const uint8_t code[] = { + 0x90, /* nop */ + 0xc7, 0x00, 0x44, 0x33, 0x22, 0x11, /* mov [rax], 0x11223344 */ + }; + const uint64_t dr7 = 1U | (1U << 2) | (1U << 20) | (3U << 22); + X86IntrCapture capture = {0}; + uc_engine *source; + uc_engine *destination; + uc_context *context; + uint64_t breakpoint_address = code_start; + uint64_t disabled = 0; + + uc_common_setup(&source, UC_ARCH_X86, UC_MODE_64, (const char *)code, + sizeof(code)); + uc_common_setup(&destination, UC_ARCH_X86, UC_MODE_64, + (const char *)code, sizeof(code)); + OK(uc_mem_map(source, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_map(destination, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_reg_write(source, UC_X86_REG_RAX, &data_address)); + OK(uc_reg_write(source, UC_X86_REG_DR0, &breakpoint_address)); + OK(uc_reg_write(source, UC_X86_REG_DR1, &data_address)); + OK(uc_reg_write(source, UC_X86_REG_DR7, &dr7)); + OK(uc_context_alloc(source, &context)); + OK(uc_context_save(source, context)); + + OK(uc_reg_write(source, UC_X86_REG_DR7, &disabled)); + OK(uc_context_restore(source, context)); + test_x86_context_check_debug_state(source, &capture); + + capture = (X86IntrCapture){0}; + OK(uc_close(source)); + OK(uc_context_restore(destination, context)); + test_x86_context_check_debug_state(destination, &capture); + + OK(uc_context_free(context)); + OK(uc_close(destination)); +} + // AARCH64 inline the read while s390x won't split the access. Though not tested // on other hosts but we restrict a bit more. #if !defined(TARGET_READ_INLINED) && defined(BOOST_LITTLE_ENDIAN) @@ -3181,17 +4136,92 @@ static void test_x86_read_virtual(void) uc_vmem_read(uc, 0x1000, UC_PROT_WRITE, &tmp, sizeof(tmp))); TEST_CHECK(parrent == 60); TEST_CHECK(child == 42); + + OK(uc_context_free(context)); + OK(uc_close(uc)); } static bool test_x86_vtlb_callback(uc_engine *uc, uint64_t addr, uc_mem_type type, uc_tlb_entry *result, void *user_data) { + if (user_data != NULL) { + (*(uint32_t *)user_data)++; + } + result->paddr = addr; + result->perms = UC_PROT_ALL; + return true; +} + +typedef struct X86VtlbExitData { + uc_err stop_error; + uint32_t data_fill_count; + bool stop; +} X86VtlbExitData; + +static bool test_x86_vtlb_exit_callback(uc_engine *uc, uint64_t addr, + uc_mem_type type, + uc_tlb_entry *result, void *user_data) +{ + X86VtlbExitData *data = (X86VtlbExitData *)user_data; + result->paddr = addr; result->perms = UC_PROT_ALL; + if (type == UC_MEM_READ) { + data->data_fill_count++; + if (data->stop) { + data->stop_error = uc_emu_stop(uc); + } + } return true; } +static void test_x86_cputlb_vtlb_fill_exit(void) +{ + const uint8_t code[] = { + 0xa1, 0x00, 0x00, 0x20, 0x00, /* mov eax,[0x200000] */ + 0x43, /* inc ebx */ + }; + const uint32_t memory_value = 0x78563412; + const uint32_t initial_eax = 0xdeadbeef; + X86VtlbExitData data = {.stop = true}; + uint32_t eax = initial_eax; + uint32_t ebx = 0; + uint32_t eip = 0; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, (const char *)code, + sizeof(code)); + OK(uc_mem_map(uc, 0x200000, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, 0x200000, &memory_value, sizeof(memory_value))); + OK(uc_ctl_tlb_mode(uc, UC_TLB_VIRTUAL)); + OK(uc_hook_add(uc, &hook, UC_HOOK_TLB_FILL, + test_x86_vtlb_exit_callback, &data, 1, 0)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(data.stop_error); + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx)); + TEST_CHECK(data.data_fill_count == 1); + TEST_CHECK(eip == code_start); + TEST_CHECK(eax == initial_eax); + TEST_CHECK(ebx == 0); + + data.stop = false; + OK(uc_emu_start(uc, eip, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx)); + TEST_CHECK(data.data_fill_count == 1); + TEST_CHECK(eax == memory_value); + TEST_CHECK(ebx == 1); + + OK(uc_close(uc)); +} + static void test_x86_vtlb(void) { uc_engine *uc; @@ -3215,45 +4245,262 @@ static void test_x86_vtlb(void) OK(uc_close(uc)); } -static void test_x86_segmentation(void) +static void test_x86_vtlb_conflict_growth(void) { + const uint64_t data_address = 0x200000; + const uint64_t page_size = 0x1000; + const uint64_t page_count = 512; + const uint64_t rounds = 3; + const uint8_t code[] = { + 0x48, 0x89, 0xde, /* mov rsi, rbx */ + 0x4c, 0x89, 0xc2, /* mov rdx, r8 */ + 0x48, 0x03, 0x06, /* add rax, [rsi] */ + 0x48, 0x81, 0xc6, 0x00, 0x10, 0x00, 0x00, + /* add rsi, 0x1000 */ + 0x48, 0xff, 0xca, /* dec rdx */ + 0x75, 0xf1, /* jnz inner */ + 0x48, 0xff, 0xc9, /* dec rcx */ + 0x75, 0xe6, /* jnz outer */ + }; + uint64_t rax = 0; + uint64_t rbx = data_address; + uint64_t rcx = rounds; + uint64_t r8 = page_count; + uint64_t one = 1; + uint32_t fill_count = 0; uc_engine *uc; - uint16_t fs = 0x53; - uc_x86_mmr gdtr = {0, 0xfffff8076d962000, 0x57, 0}; + uc_hook hook; + uint64_t i; - OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); - OK(uc_reg_write(uc, UC_X86_REG_GDTR, &gdtr)); - uc_assert_err(UC_ERR_EXCEPTION, uc_reg_write(uc, UC_X86_REG_FS, &fs)); - OK(uc_close(uc)); -} + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, (const char *)code, + sizeof(code)); + OK(uc_mem_map(uc, data_address, page_count * page_size, UC_PROT_ALL)); + for (i = 0; i < page_count; i++) { + OK(uc_mem_write(uc, data_address + i * page_size, &one, + sizeof(one))); + } -static void test_x86_0xff_lcall_callback(uc_engine *uc, uint64_t address, - uint32_t size, void *user_data) -{ - // do nothing - return; + OK(uc_ctl_tlb_mode(uc, UC_TLB_VIRTUAL)); + OK(uc_hook_add(uc, &hook, UC_HOOK_TLB_FILL, test_x86_vtlb_callback, + &fill_count, 1, 0)); + OK(uc_reg_write(uc, UC_X86_REG_RAX, &rax)); + OK(uc_reg_write(uc, UC_X86_REG_RBX, &rbx)); + OK(uc_reg_write(uc, UC_X86_REG_RCX, &rcx)); + OK(uc_reg_write(uc, UC_X86_REG_R8, &r8)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_RAX, &rax)); + TEST_CHECK(rax == page_count * rounds); + TEST_CHECK(fill_count < page_count * rounds); + + OK(uc_close(uc)); } -// This aborts prior to a7a5d187e77f7853755eff4768658daf8095c3b7 -static void test_x86_0xff_lcall(void) +static void test_x86_vtlb_stride_conflict_growth(void) { + const uint64_t data_address = 0x200000; + const uint64_t stride = 0x100000; + const uint64_t page_count = 16; + const uint64_t rounds = 64; + const uint8_t code[] = { + 0x48, 0x8b, 0x06, /* mov rax, [rsi] */ + 0x48, 0x81, 0xc6, 0x00, 0x00, 0x10, 0x00, + /* add rsi, 0x100000 */ + 0x48, 0x39, 0xfe, /* cmp rsi, rdi */ + 0x75, 0x03, /* jne no_wrap */ + 0x48, 0x89, 0xde, /* mov rsi, rbx */ + 0x48, 0xff, 0xc9, /* dec rcx */ + 0x75, 0xe9, /* jnz loop */ + }; + uint64_t rax = 0; + uint64_t rbx = data_address; + uint64_t rcx = page_count * rounds; + uint64_t rdi = data_address + stride * page_count; + uint64_t rsi = data_address; + uint64_t remaining; + uint64_t value = 1; + uint32_t fill_count = 0; + uint64_t i; uc_engine *uc; - uc_hook hk; - const char code[] = - "\xB8\x01\x00\x00\x00\xBB\x01\x00\x00\x00\xB9\x01\x00\x00\x00\xFF\xDD" - "\xBA\x01\x00\x00\x00\xB8\x02\x00\x00\x00\xBB\x02\x00\x00\x00"; - // Taken from #1842 - // 0: b8 01 00 00 00 mov eax,0x1 - // 5: bb 01 00 00 00 mov ebx,0x1 - // a: b9 01 00 00 00 mov ecx,0x1 - // f: ff (bad) - // 10: dd ba 01 00 00 00 fnstsw WORD PTR [edx+0x1] - // 16: b8 02 00 00 00 mov eax,0x2 - // 1b: bb 02 00 00 00 mov ebx,0x2 + uc_hook hook; - uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, (const char *)code, + sizeof(code)); + OK(uc_mem_map(uc, data_address, stride * page_count, UC_PROT_ALL)); + for (i = 0; i < page_count; i++) { + OK(uc_mem_write(uc, data_address + i * stride, &value, + sizeof(value))); + } - OK(uc_hook_add(uc, &hk, UC_HOOK_CODE, test_x86_0xff_lcall_callback, NULL, 1, + OK(uc_ctl_tlb_mode(uc, UC_TLB_VIRTUAL)); + OK(uc_hook_add(uc, &hook, UC_HOOK_TLB_FILL, test_x86_vtlb_callback, + &fill_count, 1, 0)); + OK(uc_reg_write(uc, UC_X86_REG_RAX, &rax)); + OK(uc_reg_write(uc, UC_X86_REG_RBX, &rbx)); + OK(uc_reg_write(uc, UC_X86_REG_RCX, &rcx)); + OK(uc_reg_write(uc, UC_X86_REG_RDI, &rdi)); + OK(uc_reg_write(uc, UC_X86_REG_RSI, &rsi)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_RCX, &remaining)); + OK(uc_reg_read(uc, UC_X86_REG_RAX, &rax)); + + TEST_CHECK(remaining == 0); + TEST_CHECK(rax == value); + TEST_CHECK_(fill_count < page_count * rounds + 1, + "fill_count=%u", fill_count); + + OK(uc_close(uc)); +} + +static void test_x86_vtlb_victim_memory_hook(uc_engine *uc, + uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + (*(uint32_t *)user_data)++; +} + +static void test_x86_vtlb_hooked_victim_hit(void) +{ + const uint64_t address_a = 0x200000; + const uint64_t address_b = address_a + 0x100000; + const uint64_t value_a = 7; + const uint64_t value_b = 11; + const uint8_t code[] = { + 0x48, 0x8b, 0x03, /* mov rax, [rbx] */ + 0x48, 0x8b, 0x11, /* mov rdx, [rcx] */ + 0x48, 0x03, 0x03, /* add rax, [rbx] */ + }; + uint64_t rax = 0; + uint64_t rbx = address_a; + uint64_t rcx = address_b; + uint64_t rdx = 0; + uint32_t fill_count = 0; + uint32_t hook_count = 0; + uc_engine *uc; + uc_hook fill_hook; + uc_hook read_hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, (const char *)code, + sizeof(code)); + OK(uc_mem_map(uc, address_a, 0x1000, UC_PROT_ALL)); + OK(uc_mem_map(uc, address_b, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, address_a, &value_a, sizeof(value_a))); + OK(uc_mem_write(uc, address_b, &value_b, sizeof(value_b))); + + OK(uc_ctl_tlb_mode(uc, UC_TLB_VIRTUAL)); + OK(uc_hook_add(uc, &fill_hook, UC_HOOK_TLB_FILL, + test_x86_vtlb_callback, &fill_count, 1, 0)); + OK(uc_hook_add(uc, &read_hook, UC_HOOK_MEM_READ, + test_x86_vtlb_victim_memory_hook, &hook_count, + address_a, address_a + 0xfff)); + OK(uc_reg_write(uc, UC_X86_REG_RBX, &rbx)); + OK(uc_reg_write(uc, UC_X86_REG_RCX, &rcx)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_RAX, &rax)); + OK(uc_reg_read(uc, UC_X86_REG_RDX, &rdx)); + + TEST_CHECK(rax == value_a * 2); + TEST_CHECK(rdx == value_b); + TEST_CHECK(hook_count == 2); + TEST_CHECK(fill_count == 3); + + OK(uc_close(uc)); +} + +typedef struct { + uint64_t virtual_address; + uint64_t physical_address; +} X86HighPaddrTlbData; + +static bool test_x86_vtlb_high_paddr_callback(uc_engine *uc, uint64_t address, + uc_mem_type type, + uc_tlb_entry *result, + void *user_data) +{ + X86HighPaddrTlbData *data = (X86HighPaddrTlbData *)user_data; + + if (address == data->virtual_address) { + result->paddr = data->physical_address; + } else { + result->paddr = address; + } + result->perms = UC_PROT_ALL; + return true; +} + +static void test_x86_vtlb_32bit_high_paddr(void) +{ + const uint64_t virtual_address = 0x2000; + const uint64_t physical_address = UINT64_C(0x100000000); + const char code[] = "\xa1\x00\x20\x00\x00"; /* mov eax, [0x2000] */ + const uint32_t expected = 0x89abcdef; + X86HighPaddrTlbData data = { + .virtual_address = virtual_address, + .physical_address = physical_address, + }; + uint64_t translated; + uint32_t eax; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); + OK(uc_mem_map(uc, physical_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, physical_address, &expected, sizeof(expected))); + OK(uc_ctl_tlb_mode(uc, UC_TLB_VIRTUAL)); + OK(uc_hook_add(uc, &hook, UC_HOOK_TLB_FILL, + test_x86_vtlb_high_paddr_callback, &data, 1, 0)); + + OK(uc_vmem_translate(uc, virtual_address, UC_PROT_READ, &translated)); + TEST_CHECK(translated == physical_address); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 1)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == expected); + + OK(uc_close(uc)); +} + +static void test_x86_segmentation(void) +{ + uc_engine *uc; + uint16_t fs = 0x53; + uc_x86_mmr gdtr = {0, 0xfffff8076d962000, 0x57, 0}; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_reg_write(uc, UC_X86_REG_GDTR, &gdtr)); + uc_assert_err(UC_ERR_EXCEPTION, uc_reg_write(uc, UC_X86_REG_FS, &fs)); + OK(uc_close(uc)); +} + +static void test_x86_0xff_lcall_callback(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + // do nothing + return; +} + +// This aborts prior to a7a5d187e77f7853755eff4768658daf8095c3b7 +static void test_x86_0xff_lcall(void) +{ + uc_engine *uc; + uc_hook hk; + const char code[] = + "\xB8\x01\x00\x00\x00\xBB\x01\x00\x00\x00\xB9\x01\x00\x00\x00\xFF\xDD" + "\xBA\x01\x00\x00\x00\xB8\x02\x00\x00\x00\xBB\x02\x00\x00\x00"; + // Taken from #1842 + // 0: b8 01 00 00 00 mov eax,0x1 + // 5: bb 01 00 00 00 mov ebx,0x1 + // a: b9 01 00 00 00 mov ecx,0x1 + // f: ff (bad) + // 10: dd ba 01 00 00 00 fnstsw WORD PTR [edx+0x1] + // 16: b8 02 00 00 00 mov eax,0x2 + // 1b: bb 02 00 00 00 mov ebx,0x2 + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); + + OK(uc_hook_add(uc, &hk, UC_HOOK_CODE, test_x86_0xff_lcall_callback, NULL, 1, 0)); uc_assert_err( @@ -3263,11 +4510,10 @@ static void test_x86_0xff_lcall(void) OK(uc_close(uc)); } -static bool test_x86_64_not_overwriting_tmp0_for_pc_update_cb( - uc_engine *uc, uc_mem_type type, uint64_t address, int size, uint64_t value, +static void test_x86_64_not_overwriting_tmp0_for_pc_update_cb( + uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, void *user_data) { - return true; } // https://github.com/unicorn-engine/unicorn/issues/1717 @@ -3499,13 +4745,12 @@ static void test_rex_x64(void) } } -static bool test_x86_ro_segfault_cb(uc_engine *uc, uc_mem_type type, - uint64_t address, int size, uint64_t value, +static void test_x86_ro_segfault_cb(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, void *user_data) { const char code[] = "\xA1\x00\x10\x00\x00\xA1\x00\x10\x00\x00"; OK(uc_mem_write(uc, address, code, sizeof(code) - 1)); - return true; } static void test_x86_ro_segfault(void) @@ -3558,7 +4803,7 @@ static void test_x86_vpermilps_null_ptr_call(void) OK(uc_close(uc)); } -static bool test_x86_hook_insn_rdtsc_cb(uc_engine *uc, void *user_data) +static int test_x86_hook_insn_rdtsc_cb(uc_engine *uc, void *user_data) { uint64_t h = 0x00000000FEDCBA98; OK(uc_reg_write(uc, UC_X86_REG_RDX, &h)); @@ -3595,7 +4840,7 @@ static void test_x86_hook_insn_rdtsc(void) OK(uc_close(uc)); } -static bool test_x86_hook_insn_rdtscp_cb(uc_engine *uc, void *user_data) +static int test_x86_hook_insn_rdtscp_cb(uc_engine *uc, void *user_data) { uint64_t h = 0x0000000001234567; OK(uc_reg_write(uc, UC_X86_REG_RDX, &h)); @@ -3718,6 +4963,113 @@ static void test_x86_hook_insn_rdmsr(void) OK(uc_close(uc)); } +static int test_x86_count_filtered_insn_hook(uc_engine *uc, void *user_data) +{ + uint32_t *count = (uint32_t *)user_data; + + (*count)++; + return 1; +} + +static void test_x86_filtered_insn_hooks(void) +{ + static const struct { + uint8_t code[3]; + size_t size; + int insn; + } cases[] = { + {{0x0f, 0xa2, 0x00}, 2, UC_X86_INS_CPUID}, + {{0x0f, 0x31, 0x00}, 2, UC_X86_INS_RDTSC}, + {{0x0f, 0x01, 0xf9}, 3, UC_X86_INS_RDTSCP}, + {{0x0f, 0x30, 0x00}, 2, UC_X86_INS_WRMSR}, + {{0x0f, 0x32, 0x00}, 2, UC_X86_INS_RDMSR}, + }; + static const int insns[] = { + UC_X86_INS_CPUID, + UC_X86_INS_RDTSC, + UC_X86_INS_RDTSCP, + UC_X86_INS_WRMSR, + UC_X86_INS_RDMSR, + }; + uint32_t counts[sizeof(insns) / sizeof(insns[0])] = { 0 }; + uc_hook hooks[sizeof(insns) / sizeof(insns[0])]; + uc_engine *uc; + size_t i; + size_t j; + + OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_X86_HASWELL)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + OK(uc_mem_write(uc, code_start + i * 0x10, + cases[i].code, cases[i].size)); + } + for (i = 0; i < sizeof(insns) / sizeof(insns[0]); i++) { + OK(uc_hook_add(uc, &hooks[i], UC_HOOK_INSN, + test_x86_count_filtered_insn_hook, &counts[i], + 1, 0, insns[i])); + } + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + uint64_t address = code_start + i * 0x10; + + OK(uc_emu_start(uc, address, address + cases[i].size, 0, 0)); + for (j = 0; j < sizeof(counts) / sizeof(counts[0]); j++) { + TEST_CHECK_(counts[j] == (j <= i ? 1u : 0u), + "case=%zu hook=%zu count=%u", i, j, counts[j]); + } + } + + OK(uc_close(uc)); +} + +static void test_x86_count_filtered_system_hook(uc_engine *uc, + void *user_data) +{ + uint32_t *count = (uint32_t *)user_data; + + (*count)++; +} + +static void test_x86_filtered_system_hooks_one(const uint8_t code[2], + int expected_insn, + int unrelated_insn) +{ + uint32_t expected_count = 0; + uint32_t unrelated_count = 0; + uc_engine *uc; + uc_hook expected_hook; + uc_hook unrelated_hook; + + uc_common_setup(&uc, UC_ARCH_X86, + expected_insn == UC_X86_INS_SYSCALL ? + UC_MODE_64 : UC_MODE_32, + (const char *)code, 2); + OK(uc_hook_add(uc, &expected_hook, UC_HOOK_INSN, + test_x86_count_filtered_system_hook, &expected_count, + 1, 0, expected_insn)); + OK(uc_hook_add(uc, &unrelated_hook, UC_HOOK_INSN, + test_x86_count_filtered_system_hook, &unrelated_count, + 1, 0, unrelated_insn)); + + OK(uc_emu_start(uc, code_start, code_start + 2, 0, 0)); + TEST_CHECK(expected_count == 1); + TEST_CHECK(unrelated_count == 0); + + OK(uc_close(uc)); +} + +static void test_x86_filtered_system_hooks(void) +{ + const uint8_t syscall_code[] = {0x0f, 0x05}; + const uint8_t sysenter_code[] = {0x0f, 0x34}; + + test_x86_filtered_system_hooks_one(syscall_code, UC_X86_INS_SYSCALL, + UC_X86_INS_SYSENTER); + test_x86_filtered_system_hooks_one(sysenter_code, UC_X86_INS_SYSENTER, + UC_X86_INS_SYSCALL); +} + static void test_x86_dr7(void) { uc_engine *uc; @@ -3759,16 +5111,1644 @@ static void test_x86_hook_block(void) OK(uc_close(uc)); } -static bool test_x86_mem_hooks_pc_guarante_mem(uc_engine *uc, uc_mem_type type, - uint64_t addr, int size, - int64_t val, void *data) +static void test_x86_count_memory_hook(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) { - if (addr >= code_start + code_len) { - uint32_t eip; - OK(uc_reg_read(uc, UC_X86_REG_EIP, (void*)&eip)); - TEST_CHECK(eip == code_start + 1); + uint32_t *count = (uint32_t *)user_data; + + (*count)++; +} + +typedef struct { + uc_hook hook; + uint32_t count; +} X86SelfDeletingHookData; + +static void test_x86_self_deleting_memory_hook(uc_engine *uc, + uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + X86SelfDeletingHookData *data = + (X86SelfDeletingHookData *)user_data; + + data->count++; + OK(uc_hook_del(uc, data->hook)); +} + +static void test_x86_memory_hook_add_delete_after_translation(void) +{ + const uint64_t data_address = 0x200000; + const char code[] = "\x8b\x03"; /* mov eax, [ebx] */ + const uint32_t value = 0x44332211; + uint32_t hook_count = 0; + uint32_t ebx = (uint32_t)data_address; + uint32_t eax; + uint32_t virtual_value; + X86SelfDeletingHookData self_deleting = { 0 }; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, &value, sizeof(value))); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 1)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == value); + + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, + test_x86_count_memory_hook, &hook_count, data_address, + data_address + sizeof(value) - 1)); + OK(uc_vmem_read(uc, data_address, UC_PROT_READ, &virtual_value, + sizeof(virtual_value))); + TEST_CHECK(virtual_value == value); + TEST_CHECK(hook_count == 0); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 1)); + TEST_CHECK(hook_count == 1); + + OK(uc_hook_del(uc, hook)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 1)); + TEST_CHECK(hook_count == 1); + + OK(uc_hook_add(uc, &self_deleting.hook, UC_HOOK_MEM_READ, + test_x86_self_deleting_memory_hook, &self_deleting, + data_address, data_address + sizeof(value) - 1)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 1)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 1)); + TEST_CHECK(self_deleting.count == 1); + + OK(uc_close(uc)); +} + +typedef struct X86MemoryHookMutationData { + int hook_type; + uint64_t address; + uc_hook added_hook; + uc_hook deleted_hook; + uint32_t log[8]; + uint32_t log_count; + bool mutated; +} X86MemoryHookMutationData; + +static void test_x86_log_added_memory_hook(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + X86MemoryHookMutationData *data = + (X86MemoryHookMutationData *)user_data; + + data->log[data->log_count++] = 3; +} + +static void test_x86_unreachable_memory_hook(uc_engine *uc, + uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + X86MemoryHookMutationData *data = + (X86MemoryHookMutationData *)user_data; + + data->log[data->log_count++] = 9; +} + +static void test_x86_mutate_memory_hooks(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + X86MemoryHookMutationData *data = + (X86MemoryHookMutationData *)user_data; + + data->log[data->log_count++] = 1; + if (!data->mutated) { + data->mutated = true; + OK(uc_hook_del(uc, data->deleted_hook)); + OK(uc_hook_add(uc, &data->added_hook, data->hook_type, + test_x86_log_added_memory_hook, data, + data->address, data->address + 3)); + } +} + +static void test_x86_memory_hook_mutation_same_dispatch_one(int hook_type) +{ + const uint64_t data_address = 0x200000; + const uint8_t read_code[] = { + 0xa1, 0x00, 0x00, 0x20, 0x00, + 0xa1, 0x00, 0x00, 0x20, 0x00, + }; + const uint8_t write_code[] = { + 0xa3, 0x00, 0x00, 0x20, 0x00, + 0xa3, 0x00, 0x00, 0x20, 0x00, + }; + const uint8_t *code = hook_type == UC_HOOK_MEM_WRITE ? + write_code : read_code; + uint32_t value = 0x44332211; + X86MemoryHookMutationData data = { + .hook_type = hook_type, + .address = data_address, + }; + uc_engine *uc; + uc_hook first_hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, (const char *)code, + sizeof(read_code)); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, &value, sizeof(value))); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &value)); + OK(uc_hook_add(uc, &first_hook, hook_type, + test_x86_mutate_memory_hooks, &data, + data_address, data_address + 3)); + OK(uc_hook_add(uc, &data.deleted_hook, hook_type, + test_x86_unreachable_memory_hook, &data, + data_address, data_address + 3)); + + OK(uc_emu_start(uc, code_start, + code_start + sizeof(read_code), 0, 0)); + TEST_CHECK(data.log_count == 4); + TEST_CHECK(data.log[0] == 1 && data.log[1] == 3 && + data.log[2] == 1 && data.log[3] == 3); + + OK(uc_close(uc)); +} + +static void test_x86_memory_hook_mutation_same_dispatch(void) +{ + test_x86_memory_hook_mutation_same_dispatch_one(UC_HOOK_MEM_READ); + test_x86_memory_hook_mutation_same_dispatch_one(UC_HOOK_MEM_READ_AFTER); + test_x86_memory_hook_mutation_same_dispatch_one(UC_HOOK_MEM_WRITE); +} + +typedef struct X86MemoryHookUserData { + uc_hook hook; + struct X86MemoryHookUserData *replacement; + uint32_t count; +} X86MemoryHookUserData; + +static void test_x86_memory_hook_user_data_callback( + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) +{ + X86MemoryHookUserData *data = + (X86MemoryHookUserData *)user_data; + + data->count++; + if (data->replacement != NULL) { + OK(uc_hook_set_user_data(uc, data->hook, data->replacement)); + data->replacement = NULL; + } +} + +static void test_x86_memory_hook_user_data(void) +{ + const uint64_t data_address = 0x200000; + const uint8_t code[] = { + 0xa1, 0x00, 0x00, 0x20, 0x00, + 0xa1, 0x00, 0x00, 0x20, 0x00, + }; + const uint32_t value = 0x11223344; + X86MemoryHookUserData replacement = { 0 }; + X86MemoryHookUserData initial = { + .replacement = &replacement, + }; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)code, sizeof(code)); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, &value, sizeof(value))); + OK(uc_hook_add(uc, &initial.hook, UC_HOOK_MEM_READ, + test_x86_memory_hook_user_data_callback, &initial, + data_address, data_address + sizeof(value) - 1)); + replacement.hook = initial.hook; + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + TEST_CHECK(initial.count == 1); + TEST_CHECK(replacement.count == 1); + + OK(uc_close(uc)); +} + +typedef struct X86MemoryHookFallbackData { + uint32_t count; +} X86MemoryHookFallbackData; + +static void test_x86_memory_hook_fallback_callback( + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) +{ + X86MemoryHookFallbackData *data = + (X86MemoryHookFallbackData *)user_data; + uint32_t eip; + + OK(uc_reg_read(uc, UC_X86_REG_EIP, &eip)); + TEST_CHECK(type == UC_MEM_READ); + TEST_CHECK(size == 4); + TEST_CHECK(eip == code_start + data->count * 5); + data->count++; +} + +static void test_x86_memory_hook_fallback_accesses(void) +{ + const uint64_t data_address = 0x200000; + const uint8_t code[] = { + 0xa1, 0x01, 0x00, 0x20, 0x00, + 0xa1, 0xfe, 0x0f, 0x20, 0x00, + }; + const uint32_t unaligned_value = 0x11223344; + const uint32_t cross_page_value = 0xaabbccdd; + X86MemoryHookFallbackData data = { 0 }; + uint32_t eax; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)code, sizeof(code)); + OK(uc_mem_map(uc, data_address, 0x2000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address + 1, &unaligned_value, + sizeof(unaligned_value))); + OK(uc_mem_write(uc, data_address + 0xffe, &cross_page_value, + sizeof(cross_page_value))); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, + test_x86_memory_hook_fallback_callback, &data, 1, 0)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(data.count == 2); + TEST_CHECK(eax == cross_page_value); + + OK(uc_close(uc)); +} + +typedef struct { + uint32_t expected_pc; + uint32_t count; +} X86RestoreCacheHookData; + +static void test_x86_restore_cache_memory_hook(uc_engine *uc, + uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + X86RestoreCacheHookData *data = + (X86RestoreCacheHookData *)user_data; + uint32_t pc; + + OK(uc_reg_read(uc, UC_X86_REG_EIP, &pc)); + TEST_CHECK(pc == data->expected_pc); + data->count++; +} + +static void test_x86_memory_hook_restore_cache_tb_flush(void) +{ + const uint64_t second_code_address = code_start + 0x1000; + const uint64_t data_address = 0x200000; + const uint8_t code[] = { 0x8b, 0x03 }; /* mov eax, [ebx] */ + const uint32_t value = 0x44332211; + X86RestoreCacheHookData data = { + .expected_pc = (uint32_t)code_start, + }; + uint32_t ebx = (uint32_t)data_address; + uint32_t eax; + size_t i; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, (const char *)code, + sizeof(code)); + OK(uc_mem_write(uc, second_code_address, code, sizeof(code))); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, &value, sizeof(value))); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, + test_x86_restore_cache_memory_hook, &data, + data_address, data_address + sizeof(value) - 1)); + + for (i = 0; i < 8; i++) { + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + } + + OK(uc_ctl_flush_tb(uc)); + data.expected_pc = (uint32_t)second_code_address; + for (i = 0; i < 8; i++) { + OK(uc_emu_start(uc, second_code_address, + second_code_address + sizeof(code), 0, 0)); + } + + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == value); + TEST_CHECK(data.count == 16); + + OK(uc_close(uc)); +} + +typedef struct X86CodeHookDeleteData { + uc_hook hook; + uint32_t count; +} X86CodeHookDeleteData; + +static void test_x86_count_code_hook(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + X86CodeHookDeleteData *data = + (X86CodeHookDeleteData *)user_data; + + data->count++; +} + +static void test_x86_delete_code_hook(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + X86CodeHookDeleteData *data = + (X86CodeHookDeleteData *)user_data; + + data->count++; + if (data->count == 1) { + OK(uc_hook_del(uc, data->hook)); + } +} + +static void test_x86_bounded_mid_tb_code_hook_delete(void) +{ + const uint64_t sink_address = code_start + 0x1000; + const uint8_t code[] = { + 0x40, /* inc eax */ + 0x40, /* inc eax */ + 0xe9, 0xf9, 0x0f, 0x00, 0x00, /* jmp sink */ + }; + const uint8_t sink_code[] = { 0x90 }; /* nop */ + X86CodeHookDeleteData data = { 0 }; + uint32_t eax = 0; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, (const char *)code, + sizeof(code)); + OK(uc_mem_write(uc, sink_address, sink_code, sizeof(sink_code))); + OK(uc_hook_add(uc, &data.hook, UC_HOOK_CODE, + test_x86_count_code_hook, &data, + code_start + 1, code_start + 1)); + + OK(uc_emu_start(uc, code_start, + sink_address + sizeof(sink_code), 0, 0)); + TEST_CHECK(data.count == 1); + OK(uc_hook_del(uc, data.hook)); + + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_emu_start(uc, code_start, + sink_address + sizeof(sink_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 2); + TEST_CHECK(data.count == 1); + + OK(uc_close(uc)); +} + +static void test_x86_self_deleting_single_code_hook(void) +{ + const uint8_t code[] = { 0x40, 0x40, 0x40, 0x40 }; + X86CodeHookDeleteData data = { 0 }; + uint32_t eax; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, (const char *)code, + sizeof(code)); + OK(uc_hook_add(uc, &data.hook, UC_HOOK_CODE, + test_x86_delete_code_hook, &data, 1, 0)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 4); + TEST_CHECK(data.count == 1); + + OK(uc_close(uc)); +} + +typedef struct X86CodeHookUserData { + uc_hook hook; + struct X86CodeHookUserData *replacement; + uc_err callback_update_error; + uint32_t count; +} X86CodeHookUserData; + +static void test_x86_code_hook_user_data_callback(uc_engine *uc, + uint64_t address, + uint32_t size, + void *user_data) +{ + X86CodeHookUserData *data = (X86CodeHookUserData *)user_data; + + data->count++; + if (data->replacement != NULL) { + data->callback_update_error = + uc_hook_set_user_data(uc, data->hook, data->replacement); + } +} + +static void test_x86_bounded_code_hook_user_data(void) +{ + const uint8_t code[] = { 0x40, 0x40 }; + X86CodeHookUserData replacement = { 0 }; + X86CodeHookUserData initial = { + .replacement = &replacement, + .callback_update_error = UC_ERR_OK, + }; + uint32_t eax = 0; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)code, sizeof(code)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_hook_add(uc, &initial.hook, UC_HOOK_CODE, + test_x86_code_hook_user_data_callback, &initial, + code_start + 1, code_start + 1)); + replacement.hook = initial.hook; + + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + TEST_CHECK(initial.count == 1); + TEST_CHECK(initial.callback_update_error == UC_ERR_ARG); + TEST_CHECK(replacement.count == 0); + + OK(uc_hook_set_user_data(uc, initial.hook, &replacement)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + TEST_CHECK(initial.count == 1); + TEST_CHECK(replacement.count == 1); + + OK(uc_hook_del(uc, initial.hook)); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + TEST_CHECK(replacement.count == 1); + + OK(uc_close(uc)); +} + +typedef struct X86SelfDeletingBlockData { + uc_hook hook; + uint32_t count; +} X86SelfDeletingBlockData; + +static void test_x86_self_deleting_block_callback(uc_engine *uc, + uint64_t address, + uint32_t size, + void *user_data) +{ + X86SelfDeletingBlockData *data = + (X86SelfDeletingBlockData *)user_data; + + data->count++; + OK(uc_hook_del(uc, data->hook)); +} + +static void test_x86_self_deleting_single_block_hook(void) +{ + const uint64_t target = code_start + 0x1000; + const uint8_t first_code[] = { + 0x40, + 0xe9, 0xfa, 0x0f, 0x00, 0x00, + }; + const uint8_t target_code[] = { 0x40 }; + X86SelfDeletingBlockData data = { 0 }; + uint32_t eax; + uc_engine *uc; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)first_code, sizeof(first_code)); + OK(uc_mem_write(uc, target, target_code, sizeof(target_code))); + OK(uc_hook_add(uc, &data.hook, UC_HOOK_BLOCK, + test_x86_self_deleting_block_callback, &data, 1, 0)); + + OK(uc_emu_start(uc, code_start, + target + sizeof(target_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 2); + TEST_CHECK(data.count == 1); + + OK(uc_close(uc)); +} + +typedef struct X86CodeHookMutationData { + uc_hook added_hook; + uc_hook deleted_hook; + uint32_t log[8]; + uint32_t log_count; + bool mutated; +} X86CodeHookMutationData; + +static void test_x86_log_added_code_hook(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + X86CodeHookMutationData *data = + (X86CodeHookMutationData *)user_data; + + data->log[data->log_count++] = 3; +} + +static void test_x86_add_code_hook_from_callback(uc_engine *uc, + uint64_t address, + uint32_t size, + void *user_data) +{ + X86CodeHookMutationData *data = + (X86CodeHookMutationData *)user_data; + + data->log[data->log_count++] = 2; + if (!data->mutated) { + data->mutated = true; + OK(uc_hook_add(uc, &data->added_hook, UC_HOOK_CODE, + test_x86_log_added_code_hook, data, + address, address)); + } +} + +static void test_x86_delete_later_code_hook(uc_engine *uc, + uint64_t address, + uint32_t size, + void *user_data) +{ + X86CodeHookMutationData *data = + (X86CodeHookMutationData *)user_data; + + data->log[data->log_count++] = 1; + if (!data->mutated) { + data->mutated = true; + OK(uc_hook_del(uc, data->deleted_hook)); + } +} + +static void test_x86_unreachable_code_hook(uc_engine *uc, uint64_t address, + uint32_t size, void *user_data) +{ + X86CodeHookMutationData *data = + (X86CodeHookMutationData *)user_data; + + data->log[data->log_count++] = 9; +} + +static void test_x86_code_hook_append_same_dispatch(void) +{ + const uint8_t code[] = { 0x40 }; /* inc eax */ + X86CodeHookMutationData data = { 0 }; + uc_engine *uc; + uc_hook nonmatching_hook; + uc_hook matching_hook; + uint32_t eax; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, (const char *)code, + sizeof(code)); + OK(uc_hook_add(uc, &nonmatching_hook, UC_HOOK_CODE, + test_x86_unreachable_code_hook, &data, + code_start + 0x100, code_start + 0x100)); + OK(uc_hook_add(uc, &matching_hook, UC_HOOK_CODE, + test_x86_add_code_hook_from_callback, &data, + code_start, code_start)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 1); + TEST_CHECK(data.log_count == 4); + TEST_CHECK(data.log[0] == 2 && data.log[1] == 3 && + data.log[2] == 2 && data.log[3] == 3); + + OK(uc_close(uc)); +} + +static void test_x86_code_hook_delete_later_same_dispatch(void) +{ + const uint8_t code[] = { 0x40 }; /* inc eax */ + X86CodeHookMutationData data = { 0 }; + uc_engine *uc; + uc_hook first_hook; + uint32_t eax; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, (const char *)code, + sizeof(code)); + OK(uc_hook_add(uc, &first_hook, UC_HOOK_CODE, + test_x86_delete_later_code_hook, &data, + code_start, code_start)); + OK(uc_hook_add(uc, &data.deleted_hook, UC_HOOK_CODE, + test_x86_unreachable_code_hook, &data, + code_start, code_start)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 1); + TEST_CHECK(data.log_count == 1 && data.log[0] == 1); + + OK(uc_close(uc)); +} + +typedef struct X86EdgeFlushData { + uint32_t count; + uc_tb first_current; + uc_tb first_previous; +} X86EdgeFlushData; + +static void test_x86_edge_flush_callback(uc_engine *uc, + uc_tb *current, + uc_tb *previous, + void *user_data) +{ + X86EdgeFlushData *data = (X86EdgeFlushData *)user_data; + + if (data->count == 0) { + data->first_current = *current; + data->first_previous = *previous; + OK(uc_ctl_flush_tb(uc)); + } + data->count++; +} + +static void test_x86_edge_hook_tb_flush_lifetime(void) +{ + const uint64_t second_address = code_start + 0x1000; + const uint64_t sink_address = code_start + 0x2000; + const uint8_t first_code[] = { + 0xe9, 0xfb, 0x0f, 0x00, 0x00, /* jmp second */ + }; + const uint8_t second_code[] = { + 0x40, /* inc eax */ + 0xe9, 0xfa, 0x0f, 0x00, 0x00, /* jmp sink */ + }; + const uint8_t sink_code[] = { 0x90 }; + X86EdgeFlushData data = { 0 }; + uc_engine *uc; + uc_hook hook; + uint32_t eax; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)first_code, sizeof(first_code)); + OK(uc_mem_write(uc, second_address, second_code, sizeof(second_code))); + OK(uc_mem_write(uc, sink_address, sink_code, sizeof(sink_code))); + OK(uc_hook_add(uc, &hook, UC_HOOK_EDGE_GENERATED, + test_x86_edge_flush_callback, &data, 1, 0)); + + OK(uc_emu_start(uc, code_start, second_address, 0, 0)); + OK(uc_ctl_remove_cache(uc, second_address, second_address + 1)); + OK(uc_emu_start(uc, second_address, + sink_address + sizeof(sink_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == 1); + TEST_CHECK_(data.count == 3, "count=%u", data.count); + TEST_CHECK_(data.first_previous.pc == code_start, + "previous=0x%" PRIx64, data.first_previous.pc); + TEST_CHECK_(data.first_current.pc == second_address, + "current=0x%" PRIx64, data.first_current.pc); + + OK(uc_close(uc)); +} + +typedef struct X86NestedEdgeData { + uint64_t inner_start; + uint64_t inner_end; + uint64_t previous[8]; + uint64_t current[8]; + uint32_t count; + bool nested_started; +} X86NestedEdgeData; + +static void test_x86_nested_edge_callback(uc_engine *uc, + uc_tb *current, + uc_tb *previous, + void *user_data) +{ + X86NestedEdgeData *data = (X86NestedEdgeData *)user_data; + + TEST_CHECK(data->count < 8); + data->previous[data->count] = previous->pc; + data->current[data->count] = current->pc; + data->count++; + if (!data->nested_started) { + uint32_t resume_pc = (uint32_t)current->pc; + + data->nested_started = true; + OK(uc_emu_start(uc, data->inner_start, data->inner_end, 0, 0)); + OK(uc_reg_write(uc, UC_X86_REG_EIP, &resume_pc)); + } +} + +static void test_x86_nested_edge_history(void) +{ + const uint64_t outer_target = code_start + 0x1000; + const uint64_t inner_start = code_start + 0x2000; + const uint64_t inner_end = code_start + 0x3000; + const uint8_t jump[] = { + 0xe9, 0xfb, 0x0f, 0x00, 0x00, + }; + const uint8_t nop[] = { 0x90 }; + X86NestedEdgeData data = { + .inner_start = inner_start, + .inner_end = inner_end, + }; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)jump, sizeof(jump)); + OK(uc_mem_write(uc, outer_target, nop, sizeof(nop))); + OK(uc_mem_write(uc, inner_start, jump, sizeof(jump))); + OK(uc_mem_write(uc, inner_end, nop, sizeof(nop))); + OK(uc_hook_add(uc, &hook, UC_HOOK_EDGE_GENERATED, + test_x86_nested_edge_callback, &data, 1, 0)); + + OK(uc_emu_start(uc, code_start, outer_target, 0, 0)); + TEST_CHECK(data.count >= 2); + TEST_CHECK(data.previous[0] == code_start); + TEST_CHECK(data.current[0] == outer_target); + TEST_CHECK(data.previous[1] == inner_start); + TEST_CHECK(data.current[1] == inner_end); + + OK(uc_close(uc)); +} + +typedef struct X86EdgeHistoryResetData { + uint64_t previous[4]; + uint64_t current[4]; + uint32_t count; +} X86EdgeHistoryResetData; + +static void test_x86_edge_history_reset_callback(uc_engine *uc, + uc_tb *current, + uc_tb *previous, + void *user_data) +{ + X86EdgeHistoryResetData *data = + (X86EdgeHistoryResetData *)user_data; + + TEST_CHECK(data->count < 4); + data->previous[data->count] = previous->pc; + data->current[data->count] = current->pc; + data->count++; +} + +static void test_x86_edge_hook_add_resets_history(void) +{ + const uint64_t middle = code_start + 0x1000; + const uint64_t sink = code_start + 0x2000; + const uint8_t jump[] = { + 0xe9, 0xfb, 0x0f, 0x00, 0x00, + }; + const uint8_t sink_code[] = { 0x90 }; + X86EdgeHistoryResetData data = { 0 }; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)jump, sizeof(jump)); + OK(uc_mem_write(uc, middle, jump, sizeof(jump))); + OK(uc_mem_write(uc, sink, sink_code, sizeof(sink_code))); + OK(uc_hook_add(uc, &hook, UC_HOOK_EDGE_GENERATED, + test_x86_edge_history_reset_callback, &data, 1, 0)); + OK(uc_emu_start(uc, code_start, middle, 0, 0)); + TEST_CHECK(data.count == 1); + OK(uc_hook_del(uc, hook)); + memset(&data, 0, sizeof(data)); + + OK(uc_hook_add(uc, &hook, UC_HOOK_EDGE_GENERATED, + test_x86_edge_history_reset_callback, &data, 1, 0)); + OK(uc_emu_start(uc, middle, + sink + sizeof(sink_code), 0, 0)); + TEST_CHECK_(data.count == 2, "count=%u", data.count); + TEST_CHECK_(data.previous[0] == middle, "previous=0x%" PRIx64, + data.previous[0]); + TEST_CHECK_(data.current[0] == sink, "current=0x%" PRIx64, + data.current[0]); + TEST_CHECK(data.previous[1] == sink); + TEST_CHECK(data.current[1] == sink + sizeof(sink_code)); + + OK(uc_close(uc)); +} + +typedef struct { + uint32_t read_after_count; + uint32_t write_count; +} X86ReadAfterWriteHookData; + +static void test_x86_count_read_after_write_hook(uc_engine *uc, + uc_mem_type type, + uint64_t address, int size, + int64_t value, + void *user_data) +{ + X86ReadAfterWriteHookData *data = + (X86ReadAfterWriteHookData *)user_data; + + if (type == UC_MEM_READ_AFTER) { + data->read_after_count++; + } else { + TEST_CHECK(type == UC_MEM_WRITE); + data->write_count++; + } +} + +static void test_x86_memory_write_read_after_hook_add_delete(void) +{ + const uint64_t data_address = 0x200000; + const uint8_t code[] = { + 0xa1, 0x00, 0x00, 0x20, 0x00, /* mov eax, [0x200000] */ + 0xa3, 0x04, 0x00, 0x20, 0x00, /* mov [0x200004], eax */ + }; + const uint32_t value = 0x44332211; + X86ReadAfterWriteHookData data = { 0 }; + uint32_t stored = 0; + uc_engine *uc; + uc_hook read_after_hook; + uc_hook write_hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, (const char *)code, + sizeof(code)); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, &value, sizeof(value))); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_hook_add(uc, &read_after_hook, UC_HOOK_MEM_READ_AFTER, + test_x86_count_read_after_write_hook, &data, + data_address, data_address + sizeof(value) - 1)); + OK(uc_hook_add(uc, &write_hook, UC_HOOK_MEM_WRITE, + test_x86_count_read_after_write_hook, &data, + data_address + sizeof(value), + data_address + 2 * sizeof(value) - 1)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + TEST_CHECK(data.read_after_count == 1); + TEST_CHECK(data.write_count == 1); + + OK(uc_hook_del(uc, read_after_hook)); + OK(uc_hook_del(uc, write_hook)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + TEST_CHECK(data.read_after_count == 1); + TEST_CHECK(data.write_count == 1); + OK(uc_mem_read(uc, data_address + sizeof(value), &stored, + sizeof(stored))); + TEST_CHECK(stored == value); + + OK(uc_close(uc)); +} + +typedef struct { + uint64_t address; + uint32_t value; + uint32_t count; +} X86RemapHookData; + +static void test_x86_remap_memory_hook(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + X86RemapHookData *data = (X86RemapHookData *)user_data; + + data->count++; + OK(uc_mem_unmap(uc, data->address, 0x1000)); + OK(uc_mem_map(uc, data->address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data->address, &data->value, sizeof(data->value))); +} + +static void test_x86_memory_hook_remap_revalidation(void) +{ + const uint64_t data_address = 0x200000; + const char code[] = "\x8b\x03"; /* mov eax, [ebx] */ + const uint32_t initial_value = 0x11223344; + X86RemapHookData data = { + .address = data_address, + .value = 0xaabbccdd, + }; + uint32_t ebx = (uint32_t)data_address; + uint32_t eax; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, &initial_value, sizeof(initial_value))); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, + test_x86_remap_memory_hook, &data, data_address, + data_address + sizeof(initial_value) - 1)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 1)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(data.count == 1); + TEST_CHECK(eax == data.value); + + OK(uc_close(uc)); +} + +typedef struct { + uint64_t nested_address; + uint32_t count; + bool active; +} X86NestedMemoryHookData; + +static void test_x86_nested_memory_hook(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + X86NestedMemoryHookData *data = (X86NestedMemoryHookData *)user_data; + + data->count++; + if (!data->active) { + data->active = true; + OK(uc_emu_start(uc, data->nested_address, + data->nested_address + 2, 0, 1)); + data->active = false; + } +} + +static void test_x86_memory_hook_nested_tlb_revalidation(void) +{ + const uint64_t outer_data_address = 0x200000; + const uint64_t nested_data_address = 0x300000; + const uint64_t nested_code_address = code_start + 0x1000; + const char outer_code[] = "\x8b\x03"; /* mov eax, [ebx] */ + const char nested_code[] = "\x8b\x0a"; /* mov ecx, [edx] */ + const uint32_t outer_value = 0x12345678; + const uint32_t nested_value = 0x87654321; + X86NestedMemoryHookData data = { + .nested_address = nested_code_address, + }; + uint32_t ebx = (uint32_t)outer_data_address; + uint32_t edx = (uint32_t)nested_data_address; + uint32_t eax; + uint32_t ecx; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, outer_code, + sizeof(outer_code) - 1); + OK(uc_mem_write(uc, nested_code_address, nested_code, + sizeof(nested_code) - 1)); + OK(uc_mem_map(uc, outer_data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_map(uc, nested_data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, outer_data_address, &outer_value, + sizeof(outer_value))); + OK(uc_mem_write(uc, nested_data_address, &nested_value, + sizeof(nested_value))); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_reg_write(uc, UC_X86_REG_EDX, &edx)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, + test_x86_nested_memory_hook, &data, outer_data_address, + outer_data_address + sizeof(outer_value) - 1)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(outer_code) - 1, 0, + 1)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + TEST_CHECK(data.count == 1); + TEST_CHECK(eax == outer_value); + TEST_CHECK(ecx == nested_value); + + OK(uc_close(uc)); +} + +typedef struct X86NestedFlushHookData { + uint64_t nested_address; + uint32_t count; + bool nested_started; +} X86NestedFlushHookData; + +static void test_x86_memory_hook_nested_flush_callback( + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) +{ + X86NestedFlushHookData *data = + (X86NestedFlushHookData *)user_data; + + data->count++; + if (!data->nested_started) { + data->nested_started = true; + OK(uc_ctl_flush_tb(uc)); + OK(uc_emu_start(uc, data->nested_address, + data->nested_address + 5, 0, 0)); + } +} + +static void test_x86_memory_hook_nested_tb_flush(void) +{ + const uint64_t data_address = 0x200000; + const uint64_t nested_address = code_start + 0x1000; + const uint8_t outer_code[] = { + 0x8b, 0x03, /* mov eax, [ebx] */ + 0x40, /* inc eax */ + }; + const uint8_t nested_code[] = { + 0xb9, 0x78, 0x56, 0x34, 0x12, /* mov ecx, 0x12345678 */ + }; + const uint32_t memory_value = 0x11223344; + X86NestedFlushHookData data = { + .nested_address = nested_address, + }; + uint32_t ebx = (uint32_t)data_address; + uint32_t eax; + uint32_t ecx; + uc_engine *uc; + uc_hook hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)outer_code, sizeof(outer_code)); + OK(uc_mem_write(uc, nested_address, nested_code, sizeof(nested_code))); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, &memory_value, + sizeof(memory_value))); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, + test_x86_memory_hook_nested_flush_callback, &data, + data_address, + data_address + sizeof(memory_value) - 1)); + + OK(uc_emu_start(uc, code_start, + code_start + sizeof(outer_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + TEST_CHECK_(data.count == 1, "count=%u", data.count); + TEST_CHECK_(eax == memory_value + 1, "eax=0x%x", eax); + TEST_CHECK(ecx == 0x12345678); + + OK(uc_close(uc)); +} + +typedef struct X86NestedOuterPatchData { + uint64_t nested_address; + uint64_t patch_address; + uint32_t outer_count; + uint32_t nested_count; + bool nested_started; + bool patched; +} X86NestedOuterPatchData; + +static void test_x86_nested_patch_inner_callback(uc_engine *uc, + uint64_t address, + uint32_t size, + void *user_data) +{ + X86NestedOuterPatchData *data = + (X86NestedOuterPatchData *)user_data; + const uint8_t inc_ecx = 0x41; + + data->nested_count++; + if (!data->patched) { + data->patched = true; + OK(uc_mem_write(uc, data->patch_address, &inc_ecx, + sizeof(inc_ecx))); + } +} + +static void test_x86_nested_patch_outer_callback(uc_engine *uc, + uint64_t address, + uint32_t size, + void *user_data) +{ + X86NestedOuterPatchData *data = + (X86NestedOuterPatchData *)user_data; + + data->outer_count++; + if (!data->nested_started) { + data->nested_started = true; + OK(uc_emu_start(uc, data->nested_address, + data->nested_address + 1, 0, 0)); + } +} + +static void test_x86_nested_patch_following_outer_instruction(void) +{ + const uint64_t nested_address = code_start + 0x2000; + const uint8_t outer_code[] = { + 0x40, /* inc eax */ + 0x40, /* inc eax; patched to inc ecx by nested execution */ + }; + const uint8_t nested_code[] = { 0x90 }; /* nop */ + X86NestedOuterPatchData data = { + .nested_address = nested_address, + .patch_address = code_start + 1, + }; + uint32_t eax = 0; + uint32_t ecx = 0; + uc_engine *uc; + uc_hook inner_hook; + uc_hook outer_hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)outer_code, sizeof(outer_code)); + OK(uc_mem_write(uc, nested_address, nested_code, sizeof(nested_code))); + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &ecx)); + OK(uc_hook_add(uc, &outer_hook, UC_HOOK_CODE, + test_x86_nested_patch_outer_callback, &data, + code_start, code_start)); + OK(uc_hook_add(uc, &inner_hook, UC_HOOK_CODE, + test_x86_nested_patch_inner_callback, &data, + nested_address, nested_address)); + + OK(uc_emu_start(uc, code_start, + code_start + sizeof(outer_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + TEST_CHECK_(data.outer_count == 2, "outer_count=%u", data.outer_count); + TEST_CHECK_(data.nested_count == 1, "nested_count=%u", data.nested_count); + TEST_CHECK(data.patched); + TEST_CHECK_(eax == 1, "eax=0x%x", eax); + TEST_CHECK_(ecx == 1, "ecx=0x%x", ecx); + + OK(uc_close(uc)); +} + +typedef struct X86LinkedSuccessorPatchData { + uint64_t nested_address; + uint64_t patch_address; + uint32_t successor_count; + uint32_t nested_count; + bool patched; +} X86LinkedSuccessorPatchData; + +static void test_x86_linked_successor_patch_inner_callback( + uc_engine *uc, uint64_t address, uint32_t size, void *user_data) +{ + X86LinkedSuccessorPatchData *data = + (X86LinkedSuccessorPatchData *)user_data; + const uint8_t inc_ecx = 0x41; + + data->nested_count++; + if (!data->patched) { + data->patched = true; + OK(uc_mem_write(uc, data->patch_address, &inc_ecx, + sizeof(inc_ecx))); + } +} + +static uint64_t test_x86_linked_successor_patch_mmio_read( + uc_engine *uc, uint64_t offset, unsigned int size, void *user_data) +{ + X86LinkedSuccessorPatchData *data = + (X86LinkedSuccessorPatchData *)user_data; + + data->successor_count++; + if (data->successor_count == 2) { + OK(uc_emu_start(uc, data->nested_address, + data->nested_address + 1, 0, 0)); + } + return 7; +} + +static void test_x86_nested_patch_direct_linked_successor(void) +{ + const uint64_t successor_address = code_start + 0x100; + const uint64_t nested_address = code_start + 0x2000; + const uint64_t data_address = 0x6000; + const uint8_t predecessor_code[] = { + 0xe9, 0xfb, 0x00, 0x00, 0x00, /* jmp successor_address */ + }; + const uint8_t successor_code[] = { + 0xa1, 0x00, 0x60, 0x00, 0x00, /* mov eax, [data_address] */ + 0x43, /* inc ebx */ + }; + const uint8_t nested_code[] = { 0x90 }; /* nop */ + const uint32_t memory_value = 7; + X86LinkedSuccessorPatchData data = { + .nested_address = nested_address, + .patch_address = successor_address + sizeof(successor_code) - 1, + }; + uint32_t eax = 0; + uint32_t ebx = 0; + uint32_t ecx = 0; + uc_engine *uc; + uc_hook inner_hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)predecessor_code, + sizeof(predecessor_code)); + OK(uc_mem_write(uc, successor_address, successor_code, + sizeof(successor_code))); + OK(uc_mem_write(uc, nested_address, nested_code, + sizeof(nested_code))); + OK(uc_mmio_map(uc, data_address, 0x1000, + test_x86_linked_successor_patch_mmio_read, &data, + NULL, NULL)); + OK(uc_hook_add(uc, &inner_hook, UC_HOOK_CODE, + test_x86_linked_successor_patch_inner_callback, &data, + nested_address, nested_address)); + + /* The first run installs the predecessor-to-successor direct link. */ + OK(uc_emu_start(uc, code_start, + successor_address + sizeof(successor_code), 0, 0)); + OK(uc_emu_start(uc, code_start, + successor_address + sizeof(successor_code), 0, 0)); + + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + TEST_CHECK_(data.successor_count == 3, "successor_count=%u", + data.successor_count); + TEST_CHECK_(data.nested_count == 1, "nested_count=%u", + data.nested_count); + TEST_CHECK(data.patched); + TEST_CHECK_(eax == memory_value, "eax=0x%x", eax); + TEST_CHECK_(ebx == 1, "ebx=0x%x", ebx); + TEST_CHECK_(ecx == 1, "ecx=0x%x", ecx); + + OK(uc_close(uc)); +} + +typedef struct X86LinkedBlockPatchData { + uint64_t patch_address; + uint32_t block_count; + bool patched; +} X86LinkedBlockPatchData; + +static void test_x86_linked_block_patch_outer_callback( + uc_engine *uc, uint64_t address, uint32_t size, void *user_data) +{ + X86LinkedBlockPatchData *data = + (X86LinkedBlockPatchData *)user_data; + const uint8_t inc_ecx = 0x41; + + data->block_count++; + if (data->block_count == 2 && !data->patched) { + data->patched = true; + OK(uc_mem_write(uc, data->patch_address, &inc_ecx, + sizeof(inc_ecx))); + } +} + +static void test_x86_nested_patch_direct_linked_block_hook(void) +{ + const uint64_t successor_address = code_start + 0x100; + const uint8_t predecessor_code[] = { + 0xe9, 0xfb, 0x00, 0x00, 0x00, /* jmp successor_address */ + }; + const uint8_t successor_code[] = { + 0x43, /* inc ebx */ + 0x4a, /* dec edx */ + 0x0f, 0x85, 0xf8, 0xfe, 0xff, 0xff, /* jnz code_start */ + }; + X86LinkedBlockPatchData data = { + .patch_address = successor_address, + }; + uint32_t ebx = 0; + uint32_t ecx = 0; + uint32_t edx = 2; + uc_engine *uc; + uc_hook block_hook; + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)predecessor_code, + sizeof(predecessor_code)); + OK(uc_mem_write(uc, successor_address, successor_code, + sizeof(successor_code))); + OK(uc_hook_add(uc, &block_hook, UC_HOOK_BLOCK, + test_x86_linked_block_patch_outer_callback, &data, + successor_address, successor_address)); + + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &ecx)); + OK(uc_reg_write(uc, UC_X86_REG_EDX, &edx)); + OK(uc_emu_start(uc, code_start, + successor_address + sizeof(successor_code), 0, 0)); + + OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + TEST_CHECK_(data.block_count == 3, "block_count=%u", data.block_count); + TEST_CHECK(data.patched); + TEST_CHECK_(ebx == 1, "ebx=0x%x", ebx); + TEST_CHECK_(ecx == 1, "ecx=0x%x", ecx); + + OK(uc_close(uc)); +} + +typedef struct X86VtlbActivePatchData { + uint64_t patch_address; + uint32_t callback_count; + bool patched; +} X86VtlbActivePatchData; + +static void test_x86_vtlb_active_patch_callback(uc_engine *uc, + uint64_t address, + uint32_t size, + void *user_data) +{ + X86VtlbActivePatchData *data = (X86VtlbActivePatchData *)user_data; + const uint8_t inc_ecx = 0x41; + + data->callback_count++; + if (!data->patched) { + data->patched = true; + OK(uc_mem_write(uc, data->patch_address, &inc_ecx, + sizeof(inc_ecx))); + } +} + +static bool test_x86_vtlb_allow_fetch_prot(uc_engine *uc, + uc_mem_type type, + uint64_t address, int size, + int64_t value, void *user_data) +{ + return true; +} + +static void test_x86_vtlb_active_code_rw_backing_patch(void) +{ + const uint8_t code[] = { + 0x40, /* inc eax */ + 0x40, /* inc eax; patched to inc ecx by callback */ + }; + X86VtlbActivePatchData data = { + .patch_address = code_start + 1, + }; + uint32_t eax = 0; + uint32_t ecx = 0; + uc_engine *uc; + uc_hook code_hook; + uc_hook fetch_prot_hook; + uc_hook tlb_hook; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, code_start, code_len, + UC_PROT_READ | UC_PROT_WRITE)); + OK(uc_mem_write(uc, code_start, code, sizeof(code))); + OK(uc_ctl_tlb_mode(uc, UC_TLB_VIRTUAL)); + OK(uc_hook_add(uc, &tlb_hook, UC_HOOK_TLB_FILL, + test_x86_vtlb_callback, NULL, 1, 0)); + OK(uc_hook_add(uc, &fetch_prot_hook, UC_HOOK_MEM_FETCH_PROT, + test_x86_vtlb_allow_fetch_prot, NULL, 1, 0)); + OK(uc_hook_add(uc, &code_hook, UC_HOOK_CODE, + test_x86_vtlb_active_patch_callback, &data, + code_start, code_start)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + TEST_CHECK_(data.callback_count == 2, "callback_count=%u", + data.callback_count); + TEST_CHECK_(eax == 1, "eax=0x%x", eax); + TEST_CHECK_(ecx == 1, "ecx=0x%x", ecx); + + OK(uc_close(uc)); +} + +typedef struct X86TbFlushSelfLinkData { + uint8_t *code; + uint32_t count; +} X86TbFlushSelfLinkData; + +static void test_x86_memory_hook_tb_flush_self_link_callback( + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) +{ + X86TbFlushSelfLinkData *data = + (X86TbFlushSelfLinkData *)user_data; + + data->count++; + if (data->count == 1) { + data->code[2] = 0x42; /* inc edx */ + OK(uc_ctl_flush_tb(uc)); + } +} + +static void test_x86_memory_hook_tb_flush_self_link(void) +{ + const uint64_t data_address = 0x200000; + const uint8_t original_code[] = { + 0x8b, 0x33, /* mov esi, [ebx] */ + 0x40, /* inc eax */ + 0x49, /* dec ecx */ + 0x75, 0xfa, /* jne loop */ + }; + const uint32_t memory_value = 0x11223344; + uint8_t *code_page = calloc(1, 0x1000); + X86TbFlushSelfLinkData data = { + .code = code_page, + }; + uint32_t ebx = (uint32_t)data_address; + uint32_t ecx = 4; + uint32_t eax = 0; + uint32_t edx = 0; + uc_engine *uc; + uc_hook hook; + + TEST_CHECK(code_page != NULL); + memcpy(code_page, original_code, sizeof(original_code)); + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map_ptr(uc, code_start, 0x1000, UC_PROT_ALL, code_page)); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, &memory_value, + sizeof(memory_value))); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &ecx)); + + OK(uc_emu_start(uc, code_start, + code_start + sizeof(original_code), 0, 0)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, + test_x86_memory_hook_tb_flush_self_link_callback, &data, + data_address, + data_address + sizeof(memory_value) - 1)); + + ecx = 2; + OK(uc_reg_write(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &ecx)); + OK(uc_reg_write(uc, UC_X86_REG_EDX, &edx)); + OK(uc_emu_start(uc, code_start, + code_start + sizeof(original_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_EDX, &edx)); + TEST_CHECK_(data.count == 2, "count=%u", data.count); + TEST_CHECK_(eax == 1, "eax=0x%x", eax); + TEST_CHECK_(edx == 1, "edx=0x%x", edx); + + OK(uc_close(uc)); + free(code_page); +} + +static void test_x86_code_hook_nested_flush_callback( + uc_engine *uc, uint64_t address, uint32_t size, void *user_data) +{ + X86NestedFlushHookData *data = + (X86NestedFlushHookData *)user_data; + + data->count++; + if (!data->nested_started) { + data->nested_started = true; + OK(uc_ctl_flush_tb(uc)); + if (data->nested_address != 0) { + OK(uc_emu_start(uc, data->nested_address, + data->nested_address + 5, 0, 0)); + } + } +} + +static void test_x86_code_hook_nested_tb_flush_run(unsigned int hook_count, + bool nested) +{ + const uint64_t nested_address = code_start + 0x1000; + const uint8_t outer_code[] = { 0x40 }; /* inc eax */ + const uint8_t nested_code[] = { + 0xb9, 0x78, 0x56, 0x34, 0x12, /* mov ecx, 0x12345678 */ + }; + X86NestedFlushHookData data = { + .nested_address = nested ? nested_address : 0, + }; + uint32_t eax; + uint32_t ecx; + unsigned int i; + uc_engine *uc; + uc_hook hooks[2]; + + TEST_CHECK(hook_count <= sizeof(hooks) / sizeof(hooks[0])); + + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)outer_code, sizeof(outer_code)); + OK(uc_mem_write(uc, nested_address, nested_code, sizeof(nested_code))); + for (i = 0; i < hook_count; i++) { + OK(uc_hook_add(uc, &hooks[i], UC_HOOK_CODE, + test_x86_code_hook_nested_flush_callback, &data, + code_start, code_start)); + } + + OK(uc_emu_start(uc, code_start, + code_start + sizeof(outer_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + TEST_CHECK_(data.count == hook_count, "count=%u", data.count); + TEST_CHECK_(eax == 1, "eax=0x%x", eax); + TEST_CHECK(ecx == (nested ? 0x12345678 : 0)); + + OK(uc_close(uc)); +} + +static void test_x86_code_hook_nested_tb_flush(void) +{ + test_x86_code_hook_nested_tb_flush_run(1, true); +} + +static void test_x86_multi_code_hook_nested_tb_flush(void) +{ + test_x86_code_hook_nested_tb_flush_run(2, false); +} + +static void test_x86_nested_tb_flush_retranslation(void) +{ + const uint64_t nested_address = 0x400000; + const uint8_t outer_code[] = { 0x40 }; /* inc eax */ + const uint8_t old_nested_code[] = { + 0xb9, 0x11, 0x11, 0x11, 0x11, /* mov ecx, 0x11111111 */ + }; + const uint8_t new_nested_code[] = { + 0xb9, 0x78, 0x56, 0x34, 0x12, /* mov ecx, 0x12345678 */ + }; + X86NestedFlushHookData data = { + .nested_address = nested_address, + }; + uint8_t *nested_page = calloc(1, 0x1000); + uint32_t ecx; + uc_engine *uc; + uc_hook hook; + + TEST_CHECK(nested_page != NULL); + memcpy(nested_page, old_nested_code, sizeof(old_nested_code)); + uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, + (const char *)outer_code, sizeof(outer_code)); + OK(uc_mem_map_ptr(uc, nested_address, 0x1000, UC_PROT_ALL, + nested_page)); + + OK(uc_emu_start(uc, nested_address, + nested_address + sizeof(old_nested_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + TEST_CHECK(ecx == 0x11111111); + + memcpy(nested_page, new_nested_code, sizeof(new_nested_code)); + OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, + test_x86_code_hook_nested_flush_callback, &data, + code_start, code_start)); + OK(uc_emu_start(uc, code_start, + code_start + sizeof(outer_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_ECX, &ecx)); + TEST_CHECK_(data.count == 1, "count=%u", data.count); + TEST_CHECK_(ecx == 0x12345678, "ecx=0x%x", ecx); + + OK(uc_close(uc)); + free(nested_page); +} + +typedef struct X86NestedFlushFaultData { + uint64_t nested_address; + uc_err nested_error; + uint32_t count; +} X86NestedFlushFaultData; + +static void test_x86_nested_flush_fault_callback( + uc_engine *uc, uc_mem_type type, uint64_t address, int size, + int64_t value, void *user_data) +{ + X86NestedFlushFaultData *data = + (X86NestedFlushFaultData *)user_data; + + data->count++; + OK(uc_ctl_flush_tb(uc)); + data->nested_error = + uc_emu_start(uc, data->nested_address, + data->nested_address + 6, 0, 0); +} + +static void test_x86_nested_tb_flush_fault_recovery(void) +{ + const uint64_t nested_address = code_start + 0x100; + const uint64_t data_address = 0x200000; + const uint8_t outer_code[] = { + 0x8b, 0x03, + 0x40, + }; + const uint8_t nested_code[] = { + 0x8b, 0x0d, 0x00, 0x00, 0x30, 0x00, + }; + const uint32_t memory_value = 0x11223344; + uint8_t *code_page = calloc(1, 0x1000); + X86NestedFlushFaultData data = { + .nested_address = nested_address, + }; + uint32_t ebx = (uint32_t)data_address; + uint32_t eax; + uc_engine *uc; + uc_hook hook; + + TEST_CHECK(code_page != NULL); + memcpy(code_page, outer_code, sizeof(outer_code)); + memcpy(code_page + nested_address - code_start, + nested_code, sizeof(nested_code)); + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map_ptr(uc, code_start, 0x1000, UC_PROT_ALL, code_page)); + OK(uc_mem_map(uc, data_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, data_address, &memory_value, + sizeof(memory_value))); + OK(uc_reg_write(uc, UC_X86_REG_EBX, &ebx)); + OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, + test_x86_nested_flush_fault_callback, &data, + data_address, data_address + sizeof(memory_value) - 1)); + + OK(uc_emu_start(uc, code_start, + code_start + sizeof(outer_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(data.count == 1); + TEST_CHECK(data.nested_error == UC_ERR_READ_UNMAPPED); + TEST_CHECK(eax == memory_value + 1); + + OK(uc_hook_del(uc, hook)); + code_page[2] = 0x48; + OK(uc_emu_start(uc, code_start, + code_start + sizeof(outer_code), 0, 0)); + OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax)); + TEST_CHECK(eax == memory_value - 1); + + OK(uc_close(uc)); + free(code_page); +} + +static void test_x86_mem_hooks_pc_guarante_mem(uc_engine *uc, uc_mem_type type, + uint64_t addr, int size, + int64_t val, void *data) +{ + if (addr >= code_start + code_len) { + uint32_t eip; + OK(uc_reg_read(uc, UC_X86_REG_EIP, (void*)&eip)); + TEST_CHECK(eip == code_start + 1); } - return true; } static void test_x86_mem_hooks_pc_guarantee(void) @@ -4003,6 +6983,8 @@ TEST_LIST = { {"test_x86_in", test_x86_in}, {"test_x86_out", test_x86_out}, {"test_x86_mem_hook_all", test_x86_mem_hook_all}, + {"test_x86_invalid_decode_fetch_size", + test_x86_invalid_decode_fetch_size}, {"test_x86_inc_dec_pxor", test_x86_inc_dec_pxor}, {"test_x86_avx_vpxor_ymm", test_x86_avx_vpxor_ymm}, {"test_x86_avx_vex128_zero_upper", test_x86_avx_vex128_zero_upper}, @@ -4029,6 +7011,8 @@ TEST_LIST = { test_x86_invalid_mem_read_stop_in_cb}, {"test_x86_x87_fnstenv", test_x86_x87_fnstenv}, {"test_x86_mmio", test_x86_mmio}, + {"test_x86_cputlb_read_after_exit", test_x86_cputlb_read_after_exit}, + {"test_x86_cputlb_split_mmio_exit", test_x86_cputlb_split_mmio_exit}, {"test_x86_missing_code", test_x86_missing_code}, {"test_x86_smc_xor", test_x86_smc_xor}, {"test_x86_smc_add", test_x86_smc_add}, @@ -4038,6 +7022,12 @@ TEST_LIST = { {"test_x86_hook_cpuid", test_x86_hook_cpuid}, {"test_x86_486_cpuid", test_x86_486_cpuid}, {"test_x86_qemu72_xsave_cpuid", test_x86_qemu72_xsave_cpuid}, + {"test_x86_xsave_xrstor_roundtrip", test_x86_xsave_xrstor_roundtrip}, + {"test_x86_xsaveopt_xrstor_roundtrip", test_x86_xsaveopt_xrstor_roundtrip}, + {"test_x86_xsave_xcr0_mask", test_x86_xsave_xcr0_mask}, + {"test_x86_xsave_model_gating", test_x86_xsave_model_gating}, + {"test_x86_xsave_alignment_fault", test_x86_xsave_alignment_fault}, + {"test_x86_xsave_pkru_roundtrip", test_x86_xsave_pkru_roundtrip}, {"test_x86_opmask_registers", test_x86_opmask_registers}, {"test_x86_qemu72_msr_state", test_x86_qemu72_msr_state}, {"test_x86_clear_tb_cache", test_x86_clear_tb_cache}, @@ -4058,8 +7048,11 @@ TEST_LIST = { {"test_x86_pdep32_zero_extend", test_x86_pdep32_zero_extend}, {"test_x86_pext32_zero_extend", test_x86_pext32_zero_extend}, {"test_x86_nested_emu_start", test_x86_nested_emu_start}, + {"test_x86_nested_count_state", test_x86_nested_count_state}, {"test_x86_nested_emu_stop", test_x86_nested_emu_stop}, {"test_x86_64_nested_emu_start_error", test_x86_64_nested_emu_start_error}, + {"test_x86_nested_emu_start_max_depth", + test_x86_nested_emu_start_max_depth}, {"test_x86_eflags_reserved_bit", test_x86_eflags_reserved_bit}, {"test_x86_blsi_cf", test_x86_blsi_cf}, {"test_x86_blsr_flags", test_x86_blsr_flags}, @@ -4067,13 +7060,20 @@ TEST_LIST = { {"test_x86_bzhi_index_boundary", test_x86_bzhi_index_boundary}, {"test_x86_nested_uc_emu_start_exits", test_x86_nested_uc_emu_start_exits}, {"test_x86_clear_count_cache", test_x86_clear_count_cache}, + {"test_x86_large_instruction_count", test_x86_large_instruction_count}, + {"test_x86_instruction_count_pc_change_refund", + test_x86_instruction_count_pc_change_refund}, {"test_x86_correct_address_in_small_jump_hook", test_x86_correct_address_in_small_jump_hook}, {"test_x86_correct_address_in_long_jump_hook", test_x86_correct_address_in_long_jump_hook}, {"test_x86_invalid_vex_l", test_x86_invalid_vex_l}, {"test_x86_sse_aligned_access", test_x86_sse_aligned_access}, + {"test_x86_movdqa_movdqu_alignment", + test_x86_movdqa_movdqu_alignment}, {"test_x86_data_watchpoint", test_x86_data_watchpoint}, + {"test_x86_context_debug_lifecycle", + test_x86_context_debug_lifecycle}, #if !defined(TARGET_READ_INLINED) && defined(BOOST_LITTLE_ENDIAN) {"test_x86_unaligned_access", test_x86_unaligned_access}, {"test_x86_64_unaligned_access", test_x86_64_unaligned_access}, @@ -4084,6 +7084,12 @@ TEST_LIST = { {"test_x86_mmu", test_x86_mmu}, {"test_x86_read_virtual", test_x86_read_virtual}, {"test_x86_vtlb", test_x86_vtlb}, + {"test_x86_cputlb_vtlb_fill_exit", test_x86_cputlb_vtlb_fill_exit}, + {"test_x86_vtlb_conflict_growth", test_x86_vtlb_conflict_growth}, + {"test_x86_vtlb_stride_conflict_growth", + test_x86_vtlb_stride_conflict_growth}, + {"test_x86_vtlb_hooked_victim_hit", test_x86_vtlb_hooked_victim_hit}, + {"test_x86_vtlb_32bit_high_paddr", test_x86_vtlb_32bit_high_paddr}, {"test_x86_segmentation", test_x86_segmentation}, {"test_x86_0xff_lcall", test_x86_0xff_lcall}, {"test_x86_64_not_overwriting_tmp0_for_pc_update", @@ -4098,8 +7104,63 @@ TEST_LIST = { {"test_x86_hook_insn_rdtscp", test_x86_hook_insn_rdtscp}, {"test_x86_hook_insn_wrmsr", test_x86_hook_insn_wrmsr}, {"test_x86_hook_insn_rdmsr", test_x86_hook_insn_rdmsr}, + {"test_x86_filtered_insn_hooks", test_x86_filtered_insn_hooks}, + {"test_x86_filtered_system_hooks", test_x86_filtered_system_hooks}, {"test_x86_dr7", test_x86_dr7}, {"test_x86_hook_block", test_x86_hook_block}, + {"test_x86_memory_hook_add_delete_after_translation", + test_x86_memory_hook_add_delete_after_translation}, + {"test_x86_memory_hook_mutation_same_dispatch", + test_x86_memory_hook_mutation_same_dispatch}, + {"test_x86_memory_hook_user_data", + test_x86_memory_hook_user_data}, + {"test_x86_memory_hook_fallback_accesses", + test_x86_memory_hook_fallback_accesses}, + {"test_x86_memory_hook_restore_cache_tb_flush", + test_x86_memory_hook_restore_cache_tb_flush}, + {"test_x86_bounded_mid_tb_code_hook_delete", + test_x86_bounded_mid_tb_code_hook_delete}, + {"test_x86_self_deleting_single_code_hook", + test_x86_self_deleting_single_code_hook}, + {"test_x86_bounded_code_hook_user_data", + test_x86_bounded_code_hook_user_data}, + {"test_x86_self_deleting_single_block_hook", + test_x86_self_deleting_single_block_hook}, + {"test_x86_code_hook_append_same_dispatch", + test_x86_code_hook_append_same_dispatch}, + {"test_x86_code_hook_delete_later_same_dispatch", + test_x86_code_hook_delete_later_same_dispatch}, + {"test_x86_edge_hook_tb_flush_lifetime", + test_x86_edge_hook_tb_flush_lifetime}, + {"test_x86_nested_edge_history", test_x86_nested_edge_history}, + {"test_x86_edge_hook_add_resets_history", + test_x86_edge_hook_add_resets_history}, + {"test_x86_memory_write_read_after_hook_add_delete", + test_x86_memory_write_read_after_hook_add_delete}, + {"test_x86_memory_hook_remap_revalidation", + test_x86_memory_hook_remap_revalidation}, + {"test_x86_memory_hook_nested_tlb_revalidation", + test_x86_memory_hook_nested_tlb_revalidation}, + {"test_x86_memory_hook_nested_tb_flush", + test_x86_memory_hook_nested_tb_flush}, + {"test_x86_nested_patch_following_outer_instruction", + test_x86_nested_patch_following_outer_instruction}, + {"test_x86_nested_patch_direct_linked_successor", + test_x86_nested_patch_direct_linked_successor}, + {"test_x86_nested_patch_direct_linked_block_hook", + test_x86_nested_patch_direct_linked_block_hook}, + {"test_x86_vtlb_active_code_rw_backing_patch", + test_x86_vtlb_active_code_rw_backing_patch}, + {"test_x86_memory_hook_tb_flush_self_link", + test_x86_memory_hook_tb_flush_self_link}, + {"test_x86_code_hook_nested_tb_flush", + test_x86_code_hook_nested_tb_flush}, + {"test_x86_multi_code_hook_nested_tb_flush", + test_x86_multi_code_hook_nested_tb_flush}, + {"test_x86_nested_tb_flush_retranslation", + test_x86_nested_tb_flush_retranslation}, + {"test_x86_nested_tb_flush_fault_recovery", + test_x86_nested_tb_flush_fault_recovery}, {"test_x86_mem_hooks_pc_guarantee", test_x86_mem_hooks_pc_guarantee}, {"test_x86_aaa_flags", test_x86_aaa_flags}, {"test_x86_aas_flags", test_x86_aas_flags}, diff --git a/uc.c b/uc.c index 997e3fff63..a96c5c6a48 100644 --- a/uc.c +++ b/uc.c @@ -13,6 +13,9 @@ #include // nanosleep #include +#ifdef _WIN32 +#include +#endif #include "uc_priv.h" @@ -33,7 +36,39 @@ static void clear_deleted_hooks(uc_engine *uc); static uc_err uc_snapshot(uc_engine *uc); -static uc_err uc_restore_latest_snapshot(uc_engine *uc); +static uc_err uc_restore_snapshot_preflight(uc_engine *uc, + const uc_context *context, + FlatView *restore_view); +static UcMapping *mapped_block_at(const uc_engine *uc, uint64_t address); +static void context_memory_clear(uc_context *context, bool reclaim, + bool normalize); + +typedef struct UcContextAllocation { + size_t capacity; + uc_context *context; + struct UcContextAllocation *next; +} UcContextAllocation; + +static int context_allocations_lock; +static UcContextAllocation *context_allocations; + +#ifdef UNICORN_TEST_ALLOC_FAILURE +static bool test_alloc_should_fail(uc_engine *uc, + UcTestAllocFailSite site) +{ + if (uc->test_alloc_fail_site != site) { + return false; + } + + uc->test_alloc_fail_site = UC_TEST_ALLOC_FAIL_NONE; + return true; +} +#else +#define test_alloc_should_fail(uc, site) false +#endif + +#define UC_HOOK_MEM_FAST_PATH \ + (UC_HOOK_MEM_READ | UC_HOOK_MEM_READ_AFTER | UC_HOOK_MEM_WRITE) #define UC_MTE_TAG_STORAGE_GRANULE 32 @@ -101,6 +136,32 @@ static void hook_invalidate_region(void *key, void *data, void *opaq) uc->uc_invalidate_tb(uc, region->start, region->length); } +static void request_tb_flush(uc_engine *uc) +{ + if (uc->tb_exec_depth != 0) { + uc->tb_flush_pending = true; + uc->tb_flush_deferred(uc); + } else { + uc->tb_flush(uc); + } +} + +static void hook_invalidate_range(uc_engine *uc, uint64_t begin, uint64_t end) +{ + uint64_t span; + + if (end < begin) { + request_tb_flush(uc); + return; + } + span = end - begin; + if (span >= SIZE_MAX) { + request_tb_flush(uc); + return; + } + uc->uc_invalidate_tb(uc, begin, (size_t)span + 1); +} + static void hook_delete(void *data) { struct hook *h = (struct hook *)data; @@ -196,9 +257,11 @@ const char *uc_strerror(uc_err code) case UC_ERR_MMU_READ: return "The tlb_fill hook returned false for a read (UC_ERR_MMU_READ)"; case UC_ERR_MMU_WRITE: - return "The tlb_fill hook returned false for a write (UC_ERR_MMU_WRITE)"; + return "The tlb_fill hook returned false for a write " + "(UC_ERR_MMU_WRITE)"; case UC_ERR_MMU_FETCH: - return "The tlb_fill hook returned false for a fetch (UC_ERR_MMU_FETCH)"; + return "The tlb_fill hook returned false for a fetch " + "(UC_ERR_MMU_FETCH)"; } } @@ -257,6 +320,7 @@ bool uc_arch_supported(uc_arch arch) if (unlikely(!(uc)->init_done)) { \ int __init_ret = uc_init_engine(uc); \ if (unlikely(__init_ret != UC_ERR_OK)) { \ + restore_jit_state(uc); \ return __init_ret; \ } \ } @@ -307,8 +371,6 @@ static uc_err uc_init_engine(uc_engine *uc) uc->context_content = UC_CTL_CONTEXT_CPU; - uc->unmapped_regions = g_array_new(false, false, sizeof(MemoryRegion *)); - uc->init_done = true; return UC_ERR_OK; @@ -512,6 +574,7 @@ uc_err uc_close(uc_engine *uc) { int i; MemoryRegion *mr; + UcMapping *mapping, *next_mapping; if (!uc->init_done) { free(uc); @@ -521,6 +584,17 @@ uc_err uc_close(uc_engine *uc) // Flush all translation buffers or we leak memory allocated by MMU uc->tb_flush(uc); + for (mapping = uc->mapping_records; mapping; mapping = mapping->next) { + if (mapping->active) { + uc->memory_moveout(uc, mapping, true); + mapping->active = false; + } + } + + while (uc->memory_contexts) { + context_memory_clear(uc->memory_contexts, false, false); + } + // Cleanup internally. if (uc->release) { uc->release(uc->tcg_ctx); @@ -548,12 +622,11 @@ uc_err uc_close(uc_engine *uc) mr->destructor(mr); g_free(uc->system_memory); g_free(uc->system_io); - for (size_t i = 0; i < uc->unmapped_regions->len; i++) { - mr = g_array_index(uc->unmapped_regions, MemoryRegion *, i); - mr->destructor(mr); - g_free(mr); + for (mapping = uc->mapping_records; mapping; mapping = next_mapping) { + next_mapping = mapping->next; + uc->memory_mapping_free(mapping); + g_free(mapping); } - g_array_free(uc->unmapped_regions, true); // Thread relateds. if (uc->qemu_thread_data) { @@ -577,7 +650,7 @@ uc_err uc_close(uc_engine *uc) list_clear(&uc->hook[i]); } - free(uc->mapped_blocks); + g_free(uc->mapped_blocks); g_tree_destroy(uc->ctl_exits); @@ -612,6 +685,13 @@ uc_err uc_reg_read_batch(uc_engine *uc, int const *regs, void **vals, int count) return UC_ERR_OK; } +static void uc_request_pc_change(uc_engine *uc) +{ + uc->quit_request = true; + uc->skip_sync_pc_on_exit = true; + break_translation_loop(uc); +} + UNICORN_EXPORT uc_err uc_reg_write_batch(uc_engine *uc, int const *regs, void *const *vals, int count) @@ -634,9 +714,7 @@ uc_err uc_reg_write_batch(uc_engine *uc, int const *regs, void *const *vals, } } if (setpc) { - // force to quit execution and flush TB - uc->quit_request = true; - break_translation_loop(uc); + uc_request_pc_change(uc); } restore_jit_state(uc); @@ -688,9 +766,7 @@ uc_err uc_reg_write_batch2(uc_engine *uc, int const *regs, } } if (setpc) { - // force to quit execution and flush TB - uc->quit_request = true; - break_translation_loop(uc); + uc_request_pc_change(uc); } restore_jit_state(uc); @@ -720,10 +796,7 @@ uc_err uc_reg_write(uc_engine *uc, int regid, const void *value) return err; } if (setpc) { - // force to quit execution and flush TB - uc->quit_request = true; - uc->skip_sync_pc_on_exit = true; - break_translation_loop(uc); + uc_request_pc_change(uc); } restore_jit_state(uc); @@ -751,9 +824,7 @@ uc_err uc_reg_write2(uc_engine *uc, int regid, const void *value, size_t *size) return err; } if (setpc) { - // force to quit execution and flush TB - uc->quit_request = true; - break_translation_loop(uc); + uc_request_pc_change(uc); } restore_jit_state(uc); @@ -773,10 +844,15 @@ static uint64_t memory_region_len(uc_engine *uc, MemoryRegion *mr, // check if a memory area is mapped // this is complicated because an area can overlap adjacent blocks -static bool check_mem_area(uc_engine *uc, uint64_t address, size_t size) +static bool check_mem_area(uc_engine *uc, uint64_t address, size_t size, + MemoryRegion **first_mr) { size_t count = 0, len; + if (first_mr != NULL) { + *first_mr = NULL; + } + // A wrap-around range can never be a single mapped extent. Reject it // here so the loop below can't walk from the top of the address space // back to 0 and convince callers that a wrapping range is "valid". @@ -788,6 +864,9 @@ static bool check_mem_area(uc_engine *uc, uint64_t address, size_t size) while (count < size) { MemoryRegion *mr = uc->memory_mapping(uc, address); if (mr) { + if (count == 0 && first_mr != NULL) { + *first_mr = mr; + } len = memory_region_len(uc, mr, address, size - count); count += len; address += len; @@ -800,7 +879,7 @@ static bool check_mem_area(uc_engine *uc, uint64_t address, size_t size) } uc_err uc_vmem_translate(uc_engine *uc, uint64_t address, uc_prot prot, - uint64_t *paddress) + uint64_t *paddress) { UC_INIT(uc); @@ -811,7 +890,8 @@ uc_err uc_vmem_translate(uc_engine *uc, uint64_t address, uc_prot prot, } // The sparc mmu doesn't support probe mode - if (uc->arch == UC_ARCH_SPARC && uc->cpu->cc->tlb_fill == uc->cpu->cc->tlb_fill_cpu) { + if (uc->arch == UC_ARCH_SPARC && + uc->cpu->cc->tlb_fill == uc->cpu->cc->tlb_fill_cpu) { restore_jit_state(uc); return UC_ERR_ARG; } @@ -835,8 +915,8 @@ uc_err uc_vmem_translate(uc_engine *uc, uint64_t address, uc_prot prot, } UNICORN_EXPORT -uc_err uc_vmem_read(uc_engine *uc, uint64_t address, uc_prot prot, - void *_bytes, size_t size) +uc_err uc_vmem_read(uc_engine *uc, uint64_t address, uc_prot prot, void *_bytes, + size_t size) { size_t count = 0, len; uint8_t *bytes = _bytes; @@ -852,7 +932,8 @@ uc_err uc_vmem_read(uc_engine *uc, uint64_t address, uc_prot prot, } // The sparc mmu doesn't support probe mode - if (uc->arch == UC_ARCH_SPARC && uc->cpu->cc->tlb_fill == uc->cpu->cc->tlb_fill_cpu) { + if (uc->arch == UC_ARCH_SPARC && + uc->cpu->cc->tlb_fill == uc->cpu->cc->tlb_fill_cpu) { restore_jit_state(uc); return UC_ERR_ARG; } @@ -882,7 +963,7 @@ uc_err uc_vmem_read(uc_engine *uc, uint64_t address, uc_prot prot, UNICORN_EXPORT uc_err uc_vmem_write(uc_engine *uc, uint64_t address, uc_prot prot, - const void *_bytes, size_t size) + const void *_bytes, size_t size) { size_t count = 0, len; const uint8_t *bytes = _bytes; @@ -899,7 +980,8 @@ uc_err uc_vmem_write(uc_engine *uc, uint64_t address, uc_prot prot, } // The sparc mmu doesn't support probe mode - if (uc->arch == UC_ARCH_SPARC && uc->cpu->cc->tlb_fill == uc->cpu->cc->tlb_fill_cpu) { + if (uc->arch == UC_ARCH_SPARC && + uc->cpu->cc->tlb_fill == uc->cpu->cc->tlb_fill_cpu) { restore_jit_state(uc); return UC_ERR_ARG; } @@ -914,10 +996,10 @@ uc_err uc_vmem_write(uc_engine *uc, uint64_t address, uc_prot prot, align = uc->target_page_align; pagesize = uc->target_page_size; len = MIN(size - count, (address & ~align) + pagesize - address); - if (uc_vmem_translate(uc, address, prot, &paddr) != UC_ERR_OK) { + if (uc_vmem_translate(uc, address, prot, &paddr) != UC_ERR_OK) { restore_jit_state(uc); return UC_ERR_WRITE_PROT; - } + } if (uc_mem_write(uc, paddr, bytes, len) != UC_ERR_OK) { restore_jit_state(uc); return UC_ERR_WRITE_PROT; @@ -936,17 +1018,19 @@ uc_err uc_mem_read(uc_engine *uc, uint64_t address, void *_bytes, uint64_t size) { uint64_t count = 0, len; uint8_t *bytes = _bytes; + MemoryRegion *first_mr; UC_INIT(uc); - if (!check_mem_area(uc, address, size)) { + if (!check_mem_area(uc, address, size, &first_mr)) { restore_jit_state(uc); return UC_ERR_READ_UNMAPPED; } // memory area can overlap adjacent memory blocks while (count < size) { - MemoryRegion *mr = uc->memory_mapping(uc, address); + MemoryRegion *mr = + count == 0 ? first_mr : uc->memory_mapping(uc, address); if (mr) { len = memory_region_len(uc, mr, address, size - count); if (uc->read_mem(&uc->address_space_memory, address, bytes, len) == @@ -970,24 +1054,139 @@ uc_err uc_mem_read(uc_engine *uc, uint64_t address, void *_bytes, uint64_t size) } } +static bool ranges_overlap(uint64_t first_start, uint64_t first_size, + uint64_t second_start, uint64_t second_size) +{ + if (first_size == 0 || second_size == 0) { + return false; + } + if (first_start <= second_start) { + return second_start - first_start < first_size; + } + return first_start - second_start < second_size; +} + +static uint64_t mem_write_active_tb_mask(uc_engine *uc, MemoryRegion *mr, + uint64_t address, uint64_t size) +{ + uint64_t active_mask = 0; + unsigned int i; + + if (size == 0 || uc->tb_exec_depth == 0) { + return 0; + } + + if (mr->ram && mr->ram_block != NULL) { + MemoryRegion *container = mr; + uint64_t region_address = mr->addr; + ram_addr_t ram_address; + + while (container->container != uc->system_memory) { + container = container->container; + region_address += container->addr; + } + ram_address = mr->ram_block->offset + (address - region_address); + for (i = 0; i < UC_MAX_NESTED_LEVEL; i++) { + UcTbExecFrame *frame = &uc->tb_exec_frames[i]; + uint64_t phys_start[2]; + uint32_t phys_size[2]; + unsigned int page; + + if (!frame->active) { + continue; + } + if (frame->tb == NULL) { + active_mask |= 1ULL << i; + continue; + } + if (!uc->tb_exec_frame_resolve(uc, frame->tb, phys_start, + phys_size)) { + continue; + } + for (page = 0; page < 2; page++) { + if (ranges_overlap(ram_address, size, phys_start[page], + phys_size[page])) { + active_mask |= 1ULL << i; + break; + } + } + } + } + + return active_mask; +} + +static uint64_t all_active_tb_mask(uc_engine *uc) +{ + uint64_t active_mask = 0; + unsigned int i; + + for (i = 0; i < UC_MAX_NESTED_LEVEL; i++) { + if (uc->tb_exec_frames[i].active) { + active_mask |= 1ULL << i; + } + } + return active_mask; +} + +static void mark_active_tbs_for_exit(uc_engine *uc, uint64_t active_tb_mask) +{ + unsigned int i; + + for (i = 0; i < UC_MAX_NESTED_LEVEL; i++) { + if ((active_tb_mask & (1ULL << i)) != 0) { + uc->tb_exec_frames[i].exit_requested = true; + } + } +} + +static void mem_write_finalize_active_tbs(uc_engine *uc, + uint64_t active_tb_mask) +{ + unsigned int current_level; + + if (active_tb_mask == 0) { + return; + } + mark_active_tbs_for_exit(uc, active_tb_mask); + request_tb_flush(uc); + + if (uc->nested_level == 0) { + return; + } + current_level = uc->nested_level - 1; + if ((active_tb_mask & (1ULL << current_level)) == 0) { + return; + } + uc->quit_request = true; + break_translation_loop(uc); +} + UNICORN_EXPORT uc_err uc_mem_write(uc_engine *uc, uint64_t address, const void *_bytes, uint64_t size) { uint64_t count = 0, len; const uint8_t *bytes = _bytes; + MemoryRegion *first_mr; + uint64_t active_tb_mask = 0; UC_INIT(uc); - if (!check_mem_area(uc, address, size)) { + if (!check_mem_area(uc, address, size, &first_mr)) { restore_jit_state(uc); return UC_ERR_WRITE_UNMAPPED; } // memory area can overlap adjacent memory blocks while (count < size) { - MemoryRegion *mr = uc->memory_mapping(uc, address); + MemoryRegion *mr = + count == 0 ? first_mr : uc->memory_mapping(uc, address); if (mr) { + UcMapping *mapping = mapped_block_at(uc, address); + MemoryRegion *original_mr = mr; + uint64_t chunk_active_tb_mask = 0; + uint64_t cow_active_tb_mask = 0; uint32_t operms = mr->perms; uint64_t align = uc->target_page_align; if (!(operms & UC_PROT_WRITE)) { // write protected @@ -997,17 +1196,32 @@ uc_err uc_mem_write(uc_engine *uc, uint64_t address, const void *_bytes, } len = memory_region_len(uc, mr, address, size - count); - if (uc->snapshot_level && uc->snapshot_level > mr->priority) { - mr = uc->memory_cow(uc, mr, address & ~align, - (len + (address & align) + align) & ~align); + chunk_active_tb_mask = + mem_write_active_tb_mask(uc, mr, address, len); + if (mr->ram && uc->memory_context_count != 0 && + uc->snapshot_level > mr->priority) { + uint64_t cow_address = address & ~align; + uint64_t cow_size = (len + (address & align) + align) & ~align; + + cow_active_tb_mask = + mem_write_active_tb_mask(uc, mr, cow_address, cow_size); + assert(mapping != NULL); + mr = uc->memory_cow(uc, mapping, mr, cow_address, cow_size); if (!mr) { + if (!(operms & UC_PROT_WRITE)) { + uc->readonly_mem(original_mr, true); + } + mem_write_finalize_active_tbs(uc, active_tb_mask); + restore_jit_state(uc); return UC_ERR_NOMEM; } + active_tb_mask |= cow_active_tb_mask; } if (uc->write_mem(&uc->address_space_memory, address, bytes, len) == false) { break; } + active_tb_mask |= chunk_active_tb_mask; if (!(operms & UC_PROT_WRITE)) { // write protected // now write protect it again @@ -1022,6 +1236,8 @@ uc_err uc_mem_write(uc_engine *uc, uint64_t address, const void *_bytes, } } + mem_write_finalize_active_tbs(uc, active_tb_mask); + if (count == size) { restore_jit_state(uc); return UC_ERR_OK; @@ -1032,45 +1248,130 @@ uc_err uc_mem_write(uc_engine *uc, uint64_t address, const void *_bytes, } #define TIMEOUT_STEP 2 // microseconds +typedef enum UcEmuFrameState { + UC_EMU_FRAME_INACTIVE, + UC_EMU_FRAME_PREPARED, + UC_EMU_FRAME_ACTIVE, + UC_EMU_FRAME_DONE, + UC_EMU_FRAME_TIMED_OUT, +} UcEmuFrameState; + +static int emu_atomic_read(int *value) +{ +#ifdef _MSC_VER + return InterlockedCompareExchange((volatile LONG *)value, 0, 0); +#else + return qatomic_cmpxchg(value, 0, 0); +#endif +} + +static void emu_atomic_set(int *value, int new_value) +{ +#ifdef _MSC_VER + InterlockedExchange((volatile LONG *)value, new_value); +#else + qatomic_xchg(value, new_value); +#endif +} + +static int emu_atomic_cmpxchg(int *value, int old_value, int new_value) +{ +#ifdef _MSC_VER + return InterlockedCompareExchange((volatile LONG *)value, new_value, + old_value); +#else + return qatomic_cmpxchg(value, old_value, new_value); +#endif +} + +static bool emu_ancestor_timed_out(uc_engine *uc, int level) +{ + int i; + + for (i = 0; i < level; i++) { + if (emu_atomic_read(&uc->emu_frames[i].state) == + UC_EMU_FRAME_TIMED_OUT) { + return true; + } + } + return false; +} + static void *_timeout_fn(void *arg) { - struct uc_struct *uc = arg; - int64_t current_time = get_clock(); + UcEmuFrame *frame = (UcEmuFrame *)arg; + struct uc_struct *uc = frame->uc; - do { + while (emu_atomic_read(&frame->state) == UC_EMU_FRAME_PREPARED) { usleep(TIMEOUT_STEP); - // perhaps emulation is even done before timeout? - if (uc->emulation_done) { + } + while (emu_atomic_read(&frame->state) == UC_EMU_FRAME_ACTIVE) { + int64_t current_time = get_clock(); + + if ((uint64_t)(current_time - frame->start_time) >= frame->timeout && + emu_atomic_cmpxchg(&frame->state, UC_EMU_FRAME_ACTIVE, + UC_EMU_FRAME_TIMED_OUT) == UC_EMU_FRAME_ACTIVE) { + emu_atomic_set(&uc->timed_out, true); + uc_set_stop_request(uc, true); + if (uc->cpu) { + cpu_exit(uc->cpu); + } break; } - } while ((uint64_t)(get_clock() - current_time) < uc->timeout); - - // timeout before emulation is done? - if (!uc->emulation_done) { - uc->timed_out = true; - // force emulation to stop - uc_emu_stop(uc); + if (emu_atomic_read(&frame->state) == UC_EMU_FRAME_ACTIVE) { + usleep(TIMEOUT_STEP); + } } return NULL; } -static void enable_emu_timer(uc_engine *uc, uint64_t timeout) +#ifdef _WIN32 +static unsigned __stdcall timeout_fn_win32(void *arg) { - uc->timeout = timeout; - qemu_thread_create(uc, &uc->timer, "timeout", _timeout_fn, uc, - QEMU_THREAD_JOINABLE); + _timeout_fn(arg); + return 0; +} +#endif + +static uc_err enable_emu_timer(UcEmuFrame *frame) +{ +#ifdef _WIN32 + frame->timer_handle = + _beginthreadex(NULL, 0, timeout_fn_win32, frame, 0, NULL); + if (frame->timer_handle == 0) { + return UC_ERR_RESOURCE; + } +#else + if (qemu_thread_create(frame->uc, &frame->timer, "timeout", _timeout_fn, + frame, QEMU_THREAD_JOINABLE) != 0) { + return UC_ERR_RESOURCE; + } +#endif + frame->timer_started = true; + return UC_ERR_OK; +} + +static void join_emu_timer(UcEmuFrame *frame) +{ + if (!frame->timer_started) { + return; + } +#ifdef _WIN32 + WaitForSingleObject((HANDLE)frame->timer_handle, INFINITE); + CloseHandle((HANDLE)frame->timer_handle); + frame->timer_handle = 0; +#else + qemu_thread_join(&frame->timer); +#endif + frame->timer_started = false; } static void hook_count_cb(struct uc_struct *uc, uint64_t address, uint32_t size, void *user_data) { - // count this instruction. ah ah ah. uc->emu_counter++; - // printf(":: emu counter = %u, at %lx\n", uc->emu_counter, address); - if (uc->emu_counter > uc->emu_count) { - // printf(":: emu counter = %u, stop emulation\n", uc->emu_counter); uc_emu_stop(uc); } } @@ -1098,27 +1399,77 @@ UNICORN_EXPORT uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, uint64_t timeout, size_t count) { - uc_err err; + uc_err err = UC_ERR_OK; + bool nested_start; + size_t saved_emu_count = 0; + size_t saved_emu_counter = 0; + IcountDecr saved_icount_decr = {0}; + uint32_t saved_cflags_next_tb = 0; + uc_tb saved_last_tb = {0}; + bool saved_last_tb_valid = false; + bool saved_stop_request = false; + bool saved_quit_request = false; + bool parent_tb_exit_requested = false; + bool ancestor_timeout_expired = false; + bool frame_timed_out; + uint64_t saved_timeout = uc->timeout; + UcEmuFrame *frame; + int frame_state; + int frame_level; + + // Reject before changing lifecycle state owned by the active frame. + if (uc->nested_level >= UC_MAX_NESTED_LEVEL) { + return UC_ERR_RESOURCE; + } + nested_start = uc->nested_level != 0; + if (nested_start) { + saved_emu_count = uc->emu_count; + saved_emu_counter = uc->emu_counter; + saved_icount_decr = *uc->cpu->icount_decr_ptr; + saved_cflags_next_tb = uc->cpu->cflags_next_tb; + saved_last_tb = uc->last_tb; + saved_last_tb_valid = uc->last_tb_valid; + saved_stop_request = uc_stop_requested(uc); + saved_quit_request = uc->quit_request; + } + + // Avoid nested uc_emu_start saves wrong jit states. + if (uc->nested_level == 0) { + UC_INIT(uc); + } + + frame_level = uc->nested_level; + frame = &uc->emu_frames[frame_level]; + frame->uc = uc; + frame->timeout = timeout * 1000; // microseconds -> nanoseconds + frame->descendant_timed_out = false; + frame->timer_started = false; + emu_atomic_set(&frame->state, UC_EMU_FRAME_PREPARED); + if (timeout) { + err = enable_emu_timer(frame); + if (err != UC_ERR_OK) { + emu_atomic_set(&frame->state, UC_EMU_FRAME_DONE); + if (!nested_start) { + restore_jit_state(uc); + } + return err; + } + } + + if (nested_start) { + uc->last_tb_valid = false; + } // reset the counter uc->emu_counter = 0; uc->invalid_error = UC_ERR_OK; uc->emulation_done = false; uc->size_recur_mem = 0; - uc->timed_out = false; + emu_atomic_set(&uc->timed_out, false); uc->first_tb = true; - // Avoid nested uc_emu_start saves wrong jit states. - if (uc->nested_level == 0) { - UC_INIT(uc); - } - // Advance the nested levels. We must decrease the level count by one when // we return from uc_emu_start. - if (uc->nested_level >= UC_MAX_NESTED_LEVEL) { - // We can't support so many nested levels. - return UC_ERR_RESOURCE; - } uc->nested_level++; uint32_t begin_pc32 = READ_DWORD(begin); @@ -1209,31 +1560,31 @@ uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, #endif } uc->skip_sync_pc_on_exit = false; - uc->stop_request = false; + revert_uc_emu_stop(uc); uc->emu_count = count; - // remove count hook if counting isn't necessary - if (count <= 0 && uc->count_hook != 0) { + uc->cpu->icount_decr_ptr->u32 = 0; + uc->cpu->icount_extra = 0; + uc->cpu->icount_budget = 0; + if (uc_uses_tcg_count(uc)) { + size_t initial_count = MIN((size_t)UINT16_MAX, count); + + uc->cpu->icount_decr_ptr->u16.low = initial_count; + uc->emu_counter = count - initial_count; + } + if (count == 0 && uc->count_hook != 0) { uc_hook_del(uc, uc->count_hook); uc->count_hook = 0; - - // In this case, we have to drop all translated blocks. uc->tb_flush(uc); - } - // set up count hook to count instructions. - if (count > 0 && uc->count_hook == 0) { - uc_err err; - // callback to count instructions must be run before everything else, - // so instead of appending, we must insert the hook at the begin - // of the hook list + } else if (count != 0 && !uc_uses_tcg_count(uc) && uc->count_hook == 0) { uc->hook_insert = 1; err = uc_hook_add(uc, &uc->count_hook, UC_HOOK_CODE, hook_count_cb, NULL, 1, 0); - // restore to append mode for uc_hook_add() uc->hook_insert = 0; if (err != UC_ERR_OK) { - uc->nested_level--; - return err; + frame_state = emu_atomic_cmpxchg( + &frame->state, UC_EMU_FRAME_PREPARED, UC_EMU_FRAME_DONE); + goto frame_done; } } @@ -1243,13 +1594,67 @@ uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, uc->exits[uc->nested_level - 1] = until; } - if (timeout) { - enable_emu_timer(uc, timeout * 1000); // microseconds -> nanoseconds + frame->start_time = get_clock(); + uc->timeout = frame->timeout; + emu_atomic_set(&frame->state, UC_EMU_FRAME_ACTIVE); + if (emu_ancestor_timed_out(uc, frame_level)) { + emu_atomic_set(&uc->timed_out, true); + uc_set_stop_request(uc, true); + break_translation_loop(uc); } uc->vm_start(uc); + frame_state = emu_atomic_cmpxchg(&frame->state, UC_EMU_FRAME_ACTIVE, + UC_EMU_FRAME_DONE); +frame_done: + join_emu_timer(frame); + + ancestor_timeout_expired = emu_ancestor_timed_out(uc, frame_level); + frame_timed_out = frame->descendant_timed_out || ancestor_timeout_expired || + frame_state == UC_EMU_FRAME_TIMED_OUT; + emu_atomic_set(&uc->timed_out, frame_timed_out); + uc->nested_level--; + if (nested_start) { + UcEmuFrame *parent = &uc->emu_frames[frame_level - 1]; + + /* A nested write may have invalidated the suspended parent TB. */ + parent_tb_exit_requested = + uc->tb_exec_frames[frame_level - 1].exit_requested; + parent->descendant_timed_out |= frame_timed_out; + revert_uc_emu_stop(uc); + ancestor_timeout_expired |= emu_ancestor_timed_out(uc, frame_level); + if (ancestor_timeout_expired) { + frame_timed_out = true; + parent->descendant_timed_out = true; + emu_atomic_set(&uc->timed_out, true); + } + uc->quit_request = saved_quit_request || parent_tb_exit_requested; + uc->timeout = saved_timeout; + uc->emu_count = saved_emu_count; + uc->emu_counter = saved_emu_counter; + *uc->cpu->icount_decr_ptr = saved_icount_decr; + uc->cpu->cflags_next_tb = saved_cflags_next_tb; + uc->last_tb = saved_last_tb; + uc->last_tb_valid = saved_last_tb_valid; + if (saved_stop_request || ancestor_timeout_expired) { + uc_set_stop_request(uc, true); + } + if (saved_stop_request || saved_quit_request || + ancestor_timeout_expired || parent_tb_exit_requested) { + break_translation_loop(uc); + } + } else { + uc->emu_count = 0; + uc->cpu->icount_decr_ptr->u32 = 0; + uc->cpu->icount_extra = 0; + uc->cpu->icount_budget = 0; + } + if (uc->nested_level == 0 && uc->tb_flush_pending) { + uc->tb_flush_pending = false; + uc->tb_flush(uc); + } // emulation is done if and only if we exit the outer uc_emu_start // or we may lost uc_emu_stop @@ -1263,14 +1668,11 @@ uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, restore_jit_state(uc); } - if (timeout) { - // wait for the timer to finish - qemu_thread_join(&uc->timer); - } - // We may be in a nested uc_emu_start and thus clear invalid_error // once we are done. - err = uc->invalid_error; + if (err == UC_ERR_OK) { + err = uc->invalid_error; + } uc->invalid_error = 0; return err; } @@ -1279,7 +1681,7 @@ UNICORN_EXPORT uc_err uc_emu_stop(uc_engine *uc) { UC_INIT(uc); - uc->stop_request = true; + uc_set_stop_request(uc, true); uc_err err = break_translation_loop(uc); restore_jit_state(uc); return err; @@ -1296,7 +1698,7 @@ uc_err uc_emu_stop(uc_engine *uc) static int bsearch_mapped_blocks(const uc_engine *uc, uint64_t address) { int left, right, mid; - MemoryRegion *mapping; + UcMapping *mapping; left = 0; right = uc->mapped_block_count; @@ -1306,10 +1708,10 @@ static int bsearch_mapped_blocks(const uc_engine *uc, uint64_t address) mapping = uc->mapped_blocks[mid]; - if (mapping->end - 1 < address) { - left = mid + 1; - } else if (mapping->addr > address) { + if (address < mapping->begin) { right = mid; + } else if (address - mapping->begin >= mapping->size) { + left = mid + 1; } else { return mid; } @@ -1318,58 +1720,184 @@ static int bsearch_mapped_blocks(const uc_engine *uc, uint64_t address) return left; } +static UcMapping *mapped_block_at(const uc_engine *uc, uint64_t address) +{ + int index = bsearch_mapped_blocks(uc, address); + + if (index >= 0 && (uint32_t)index < uc->mapped_block_count) { + UcMapping *mapping = uc->mapped_blocks[index]; + + if (mapping->begin <= address && + address - mapping->begin < mapping->size) { + return mapping; + } + } + return NULL; +} + // find if a memory range overlaps with existing mapped regions -static bool memory_overlap(struct uc_struct *uc, uint64_t begin, size_t size) +static bool memory_overlap(struct uc_struct *uc, uint64_t begin, uint64_t size) { unsigned int i; - uint64_t end = begin + size - 1; i = bsearch_mapped_blocks(uc, begin); // is this the highest region with no possible overlap? - if (i >= uc->mapped_block_count) + if (i >= uc->mapped_block_count) { return false; + } - // end address overlaps this region? - if (end >= uc->mapped_blocks[i]->addr) - return true; + return ranges_overlap(begin, size, uc->mapped_blocks[i]->begin, + uc->mapped_blocks[i]->size); +} - // not found +static bool mem_map_reserve(uc_engine *uc, uint32_t count) +{ + UcMapping **mappings; + uint32_t capacity; - return false; + if (count <= uc->mapped_block_capacity) { + return true; + } + if (count > UINT32_MAX - (MEM_BLOCK_INCR - 1)) { + return false; + } + + capacity = (count + MEM_BLOCK_INCR - 1) & ~(MEM_BLOCK_INCR - 1); + if (test_alloc_should_fail(uc, UC_TEST_ALLOC_FAIL_MAPPED_BLOCKS)) { + return false; + } + mappings = g_try_new(UcMapping *, capacity); + if (!mappings) { + return false; + } + if (uc->mapped_block_count != 0) { + memcpy(mappings, uc->mapped_blocks, + sizeof(*mappings) * uc->mapped_block_count); + } + g_free(uc->mapped_blocks); + uc->mapped_blocks = mappings; + uc->mapped_block_capacity = capacity; + return true; } -// common setup/error checking shared between uc_mem_map and uc_mem_map_ptr -static uc_err mem_map(uc_engine *uc, MemoryRegion *block) +static UcMapping *mem_map_prepare(uc_engine *uc, uint64_t begin, uint64_t size) { + UcMapping *mapping; - MemoryRegion **regions; + if (!mem_map_reserve(uc, uc->mapped_block_count + 1)) { + return NULL; + } + if (test_alloc_should_fail(uc, UC_TEST_ALLOC_FAIL_MAPPING_RECORD)) { + return NULL; + } + mapping = g_try_malloc0(sizeof(*mapping)); + if (!mapping) { + return NULL; + } + mapping->owner = uc; + mapping->begin = begin; + mapping->size = size; + mapping->priority = uc->snapshot_level; + return mapping; +} + +static void mem_map_activate(uc_engine *uc, UcMapping *mapping) +{ int pos; - if (block == NULL) { - return UC_ERR_NOMEM; + assert(mapping->owner == uc); + assert(!mapping->active); + assert(uc->mapped_block_count < uc->mapped_block_capacity); + + pos = bsearch_mapped_blocks(uc, mapping->begin); + + memmove(&uc->mapped_blocks[pos + 1], &uc->mapped_blocks[pos], + sizeof(*uc->mapped_blocks) * (uc->mapped_block_count - pos)); + + uc->mapped_blocks[pos] = mapping; + uc->mapped_block_count++; + mapping->active = true; +} + +static void mem_map_insert(uc_engine *uc, UcMapping *mapping, + MemoryRegion *root) +{ + assert(root != NULL); + mapping->root = root; + mapping->regions = root; + mapping->perms = root->perms; + mapping->next = uc->mapping_records; + uc->mapping_records = mapping; + root->uc_mapping = mapping; + root->mapping_offset = 0; + mem_map_activate(uc, mapping); +} + +static void mem_map_remove(uc_engine *uc, UcMapping *mapping) +{ + int pos = bsearch_mapped_blocks(uc, mapping->begin); + + assert(pos >= 0 && (uint32_t)pos < uc->mapped_block_count); + assert(uc->mapped_blocks[pos] == mapping); + uc->mapped_block_count--; + memmove(&uc->mapped_blocks[pos], &uc->mapped_blocks[pos + 1], + sizeof(*uc->mapped_blocks) * (uc->mapped_block_count - pos)); + mapping->active = false; +} + +static void mapping_record_remove(uc_engine *uc, UcMapping *mapping) +{ + UcMapping **link = &uc->mapping_records; + + while (*link && *link != mapping) { + link = &(*link)->next; } + assert(*link == mapping); + *link = mapping->next; + mapping->next = NULL; +} - if ((uc->mapped_block_count & (MEM_BLOCK_INCR - 1)) == 0) { // time to grow - regions = (MemoryRegion **)g_realloc( - uc->mapped_blocks, - sizeof(MemoryRegion *) * (uc->mapped_block_count + MEM_BLOCK_INCR)); - if (regions == NULL) { - return UC_ERR_NOMEM; - } - uc->mapped_blocks = regions; +static void mapping_reclaim(uc_engine *uc, UcMapping *mapping) +{ + if (mapping->active) { + return; } - pos = bsearch_mapped_blocks(uc, block->addr); + uc->memory_mapping_prune(mapping); + if (mapping->context_refs != 0) { + return; + } + mapping_record_remove(uc, mapping); + uc->memory_mapping_free(mapping); + g_free(mapping); +} - // shift the array right to give space for the new pointer - memmove(&uc->mapped_blocks[pos + 1], &uc->mapped_blocks[pos], - sizeof(MemoryRegion *) * (uc->mapped_block_count - pos)); +static void mapping_reclaim_inactive(uc_engine *uc) +{ + UcMapping *mapping = uc->mapping_records; - uc->mapped_blocks[pos] = block; - uc->mapped_block_count++; + while (mapping) { + UcMapping *next = mapping->next; - return UC_ERR_OK; + if (!mapping->active && mapping->context_refs == 0) { + mapping_reclaim(uc, mapping); + } else { + uc->memory_mapping_prune(mapping); + } + mapping = next; + } +} + +static void mapping_normalize_active(uc_engine *uc) +{ + UcMapping *mapping; + + for (mapping = uc->mapping_records; mapping; mapping = mapping->next) { + if (mapping->active) { + uc->memory_mapping_normalize(mapping); + } + } } static uc_err mem_map_check(uc_engine *uc, uint64_t address, uint64_t size, @@ -1412,6 +1940,8 @@ UNICORN_EXPORT uc_err uc_mem_map(uc_engine *uc, uint64_t address, uint64_t size, uint32_t perms) { + MemoryRegion *root; + UcMapping *mapping; uc_err res; UC_INIT(uc); @@ -1422,15 +1952,28 @@ uc_err uc_mem_map(uc_engine *uc, uint64_t address, uint64_t size, return res; } - res = mem_map(uc, uc->memory_map(uc, address, size, perms)); + mapping = mem_map_prepare(uc, address, size); + if (!mapping) { + restore_jit_state(uc); + return UC_ERR_NOMEM; + } + root = uc->memory_map(uc, address, size, perms); + if (!root) { + g_free(mapping); + restore_jit_state(uc); + return UC_ERR_NOMEM; + } + mem_map_insert(uc, mapping, root); restore_jit_state(uc); - return res; + return UC_ERR_OK; } UNICORN_EXPORT uc_err uc_mem_map_ptr(uc_engine *uc, uint64_t address, uint64_t size, uint32_t perms, void *ptr) { + MemoryRegion *root; + UcMapping *mapping; uc_err res; UC_INIT(uc); @@ -1446,9 +1989,20 @@ uc_err uc_mem_map_ptr(uc_engine *uc, uint64_t address, uint64_t size, return res; } - res = mem_map(uc, uc->memory_map_ptr(uc, address, size, perms, ptr)); + mapping = mem_map_prepare(uc, address, size); + if (!mapping) { + restore_jit_state(uc); + return UC_ERR_NOMEM; + } + root = uc->memory_map_ptr(uc, address, size, perms, ptr); + if (!root) { + g_free(mapping); + restore_jit_state(uc); + return UC_ERR_NOMEM; + } + mem_map_insert(uc, mapping, root); restore_jit_state(uc); - return res; + return UC_ERR_OK; } UNICORN_EXPORT @@ -1456,6 +2010,8 @@ uc_err uc_mmio_map(uc_engine *uc, uint64_t address, uint64_t size, uc_cb_mmio_read_t read_cb, void *user_data_read, uc_cb_mmio_write_t write_cb, void *user_data_write) { + MemoryRegion *root; + UcMapping *mapping; uc_err res; UC_INIT(uc); @@ -1466,12 +2022,23 @@ uc_err uc_mmio_map(uc_engine *uc, uint64_t address, uint64_t size, return res; } - // The callbacks do not need to be checked for NULL here, as their presence - // (or lack thereof) will determine the permissions used. - res = mem_map(uc, uc->memory_map_io(uc, address, size, read_cb, write_cb, - user_data_read, user_data_write)); + mapping = mem_map_prepare(uc, address, size); + if (!mapping) { + restore_jit_state(uc); + return UC_ERR_NOMEM; + } + + /* Callback presence determines the installed MMIO permissions. */ + root = uc->memory_map_io(uc, address, size, read_cb, write_cb, + user_data_read, user_data_write); + if (!root) { + g_free(mapping); + restore_jit_state(uc); + return UC_ERR_NOMEM; + } + mem_map_insert(uc, mapping, root); restore_jit_state(uc); - return res; + return UC_ERR_OK; } // Create a backup copy of the indicated MemoryRegion. @@ -1491,8 +2058,7 @@ static uint8_t *copy_region(struct uc_struct *uc, MemoryRegion *mr) return block; } -static bool copy_mte_tags(RAMBlock *block, uint8_t **tags, - ram_addr_t *tag_size) +static bool copy_mte_tags(RAMBlock *block, uint8_t **tags, ram_addr_t *tag_size) { *tags = NULL; *tag_size = 0; @@ -1545,14 +2111,42 @@ static bool restore_mte_tags(struct uc_struct *uc, uint64_t address, } } - copy_size = (size + UC_MTE_TAG_STORAGE_GRANULE - 1) / - UC_MTE_TAG_STORAGE_GRANULE; + copy_size = + (size + UC_MTE_TAG_STORAGE_GRANULE - 1) / UC_MTE_TAG_STORAGE_GRANULE; copy_size = MIN(copy_size, tag_size - source_tag_offset); copy_size = MIN(copy_size, block->mte_tags_size); memcpy(block->mte_tags, tags + source_tag_offset, copy_size); return true; } +static bool split_region_layout(uint64_t begin, uint64_t region_size, + uint64_t address, uint64_t size, + uint64_t *left_size, uint64_t *middle_size, + uint64_t *right_size) +{ + uint64_t offset; + + /* Do not form an exclusive end: terminal mappings wrap it to zero. */ + if (address < begin) { + offset = begin - address; + if (size <= offset) { + return false; + } + size -= offset; + offset = 0; + } else { + offset = address - begin; + if (offset >= region_size) { + return false; + } + } + + *left_size = offset; + *middle_size = MIN(size, region_size - offset); + *right_size = region_size - offset - *middle_size; + return true; +} + /* This function is similar to split_region, but for MMIO memory. @@ -1561,23 +2155,28 @@ static bool restore_mte_tags(struct uc_struct *uc, uint64_t address, static bool split_mmio_region(struct uc_struct *uc, MemoryRegion *mr, uint64_t address, uint64_t size, bool do_delete) { - uint64_t begin, end, chunk_end; + uint64_t begin, middle_begin; + uint64_t region_size; uint64_t l_size, r_size, m_size; mmio_cbs backup; - chunk_end = address + size; - - // This branch also break recursion. - if (address <= mr->addr && chunk_end >= mr->end) { - return true; - } - if (size == 0) { return false; } begin = mr->addr; - end = mr->end; + region_size = (uint64_t)int128_get64(mr->size); + if (!split_region_layout(begin, region_size, address, size, &l_size, + &m_size, &r_size)) { + return false; + } + + // This branch also breaks recursion. + if (l_size == 0 && r_size == 0) { + return true; + } + + middle_begin = begin + l_size; memcpy(&backup, mr->opaque, sizeof(mmio_cbs)); @@ -1594,19 +2193,6 @@ static bool split_mmio_region(struct uc_struct *uc, MemoryRegion *mr, return false; } - // adjust some things - if (address < begin) { - address = begin; - } - if (chunk_end > end) { - chunk_end = end; - } - - // compute sub region sizes - l_size = (uint64_t)(address - begin); - r_size = (uint64_t)(end - chunk_end); - m_size = (uint64_t)(chunk_end - address); - if (l_size > 0) { if (uc_mmio_map(uc, begin, l_size, backup.read, backup.user_data_read, backup.write, backup.user_data_write) != UC_ERR_OK) { @@ -1615,14 +2201,17 @@ static bool split_mmio_region(struct uc_struct *uc, MemoryRegion *mr, } if (m_size > 0 && !do_delete) { - if (uc_mmio_map(uc, address, m_size, backup.read, backup.user_data_read, - backup.write, backup.user_data_write) != UC_ERR_OK) { + if (uc_mmio_map(uc, middle_begin, m_size, backup.read, + backup.user_data_read, backup.write, + backup.user_data_write) != UC_ERR_OK) { return false; } } if (r_size > 0) { - if (uc_mmio_map(uc, chunk_end, r_size, backup.read, + uint64_t right_begin = middle_begin + m_size; + + if (uc_mmio_map(uc, right_begin, r_size, backup.read, backup.user_data_read, backup.write, backup.user_data_write) != UC_ERR_OK) { return false; @@ -1652,31 +2241,33 @@ static bool split_region(struct uc_struct *uc, MemoryRegion *mr, { uint8_t *backup; uint32_t perms; - uint64_t begin, end, chunk_end; + uint64_t begin, middle_begin; + uint64_t region_size; uint64_t l_size, m_size, r_size; RAMBlock *block = NULL; bool prealloc = false; uint8_t *tag_backup = NULL; - ram_addr_t tag_backup_size = 0; - - chunk_end = address + size; - - // if this region belongs to area [address, address+size], - // then there is no work to do. - if (address <= mr->addr && chunk_end >= mr->end) { - return true; - } + ram_addr_t tag_backup_size = 0; if (size == 0) { // trivial case return true; } - if (address >= mr->end || chunk_end <= mr->addr) { - // impossible case + begin = mr->addr; + region_size = (uint64_t)int128_get64(mr->size); + if (!split_region_layout(begin, region_size, address, size, &l_size, + &m_size, &r_size)) { return false; } + // if this region belongs to the requested area, there is no work to do. + if (l_size == 0 && r_size == 0) { + return true; + } + + middle_begin = begin + l_size; + // Find the correct and large enough (which contains our target mr) // to create the content backup. block = mr->ram_block; @@ -1704,8 +2295,6 @@ static bool split_region(struct uc_struct *uc, MemoryRegion *mr, // save the essential information required for the split before mr gets // deleted perms = mr->perms; - begin = mr->addr; - end = mr->end; // unmap this region first, then do split it later if (uc_mem_unmap(uc, mr->addr, (uint64_t)int128_get64(mr->size)) != @@ -1720,19 +2309,6 @@ static bool split_region(struct uc_struct *uc, MemoryRegion *mr, * case 3 |---size--| */ - // adjust some things - if (address < begin) { - address = begin; - } - if (chunk_end > end) { - chunk_end = end; - } - - // compute sub region sizes - l_size = (uint64_t)(address - begin); - r_size = (uint64_t)(end - chunk_end); - m_size = (uint64_t)(chunk_end - address); - // If there are error in any of the below operations, things are too far // gone at that point to recover. Could try to remap orignal region, but // these smaller allocation just failed so no guarantee that we can recover @@ -1758,41 +2334,43 @@ static bool split_region(struct uc_struct *uc, MemoryRegion *mr, if (m_size > 0 && !do_delete) { if (!prealloc) { - if (uc_mem_map(uc, address, m_size, perms) != UC_ERR_OK) { + if (uc_mem_map(uc, middle_begin, m_size, perms) != UC_ERR_OK) { goto error; } - if (uc_mem_write(uc, address, backup + l_size, m_size) != + if (uc_mem_write(uc, middle_begin, backup + l_size, m_size) != UC_ERR_OK) { goto error; } } else { - if (uc_mem_map_ptr(uc, address, m_size, perms, backup + l_size) != - UC_ERR_OK) { + if (uc_mem_map_ptr(uc, middle_begin, m_size, perms, + backup + l_size) != UC_ERR_OK) { goto error; } } - if (!restore_mte_tags(uc, address, m_size, l_size, tag_backup, + if (!restore_mte_tags(uc, middle_begin, m_size, l_size, tag_backup, tag_backup_size)) { goto error; } } if (r_size > 0) { + uint64_t right_begin = middle_begin + m_size; + if (!prealloc) { - if (uc_mem_map(uc, chunk_end, r_size, perms) != UC_ERR_OK) { + if (uc_mem_map(uc, right_begin, r_size, perms) != UC_ERR_OK) { goto error; } - if (uc_mem_write(uc, chunk_end, backup + l_size + m_size, r_size) != - UC_ERR_OK) { + if (uc_mem_write(uc, right_begin, backup + l_size + m_size, + r_size) != UC_ERR_OK) { goto error; } } else { - if (uc_mem_map_ptr(uc, chunk_end, r_size, perms, + if (uc_mem_map_ptr(uc, right_begin, r_size, perms, backup + l_size + m_size) != UC_ERR_OK) { goto error; } } - if (!restore_mte_tags(uc, chunk_end, r_size, l_size + m_size, + if (!restore_mte_tags(uc, right_begin, r_size, l_size + m_size, tag_backup, tag_backup_size)) { goto error; } @@ -1825,7 +2403,7 @@ uc_err uc_mem_protect(struct uc_struct *uc, uint64_t address, uint64_t size, UC_INIT(uc); // snapshot and protection can't be mixed - if (uc->snapshot_level > 0) { + if (uc->memory_context_count != 0) { restore_jit_state(uc); return UC_ERR_ARG; } @@ -1857,7 +2435,7 @@ uc_err uc_mem_protect(struct uc_struct *uc, uint64_t address, uint64_t size, // check that user's entire requested block is mapped // TODO check if protected is possible // deny after cow - if (!check_mem_area(uc, address, size)) { + if (!check_mem_area(uc, address, size, NULL)) { restore_jit_state(uc); return UC_ERR_NOMEM; } @@ -1882,6 +2460,7 @@ uc_err uc_mem_protect(struct uc_struct *uc, uint64_t address, uint64_t size, remove_exec = true; } mr->perms = perms; + mr->uc_mapping->perms = perms; uc->readonly_mem(mr, (perms & UC_PROT_WRITE) == 0); } else { @@ -1892,6 +2471,7 @@ uc_err uc_mem_protect(struct uc_struct *uc, uint64_t address, uint64_t size, mr = uc->memory_mapping(uc, addr); mr->perms = perms; + mr->uc_mapping->perms = perms; } count += len; @@ -1900,9 +2480,9 @@ uc_err uc_mem_protect(struct uc_struct *uc, uint64_t address, uint64_t size, // if EXEC permission is removed, then quit TB and continue at the same // place - if (remove_exec) { + if (remove_exec && uc->nested_level != 0) { pc = uc->get_pc(uc); - if (pc < address + size && pc >= address) { + if (pc >= address && pc - address < size) { uc->quit_request = true; uc_emu_stop(uc); } @@ -1913,24 +2493,18 @@ uc_err uc_mem_protect(struct uc_struct *uc, uint64_t address, uint64_t size, } static uc_err uc_mem_unmap_snapshot(struct uc_struct *uc, uint64_t address, - uint64_t size, MemoryRegion **ret) + uint64_t size) { - MemoryRegion *mr; + UcMapping *mapping; - mr = uc->memory_mapping(uc, address); - while (mr->container != uc->system_memory) { - mr = mr->container; - } - - if (mr->addr != address || int128_get64(mr->size) != size) { + mapping = mapped_block_at(uc, address); + if (!mapping || mapping->begin != address || mapping->size != size) { return UC_ERR_ARG; } - if (ret) { - *ret = mr; - } - - uc->memory_moveout(uc, mr); + uc->memory_unmap(uc, mapping); + mem_map_remove(uc, mapping); + mapping_reclaim(uc, mapping); return UC_ERR_OK; } @@ -1940,6 +2514,7 @@ uc_err uc_mem_unmap(struct uc_struct *uc, uint64_t address, uint64_t size) { MemoryRegion *mr; uint64_t addr; + uint64_t pc; uint64_t count, len; UC_INIT(uc); @@ -1963,17 +2538,19 @@ uc_err uc_mem_unmap(struct uc_struct *uc, uint64_t address, uint64_t size) } // check that user's entire requested block is mapped - if (!check_mem_area(uc, address, size)) { + if (!check_mem_area(uc, address, size, NULL)) { restore_jit_state(uc); return UC_ERR_NOMEM; } - if (uc->snapshot_level > 0) { - uc_err res = uc_mem_unmap_snapshot(uc, address, size, NULL); + if (uc->memory_context_count != 0) { + uc_err res = uc_mem_unmap_snapshot(uc, address, size); restore_jit_state(uc); return res; } + pc = uc->get_pc(uc); + // Now we know entire region is mapped, so do the unmap // We may need to split regions if this area spans adjacent regions addr = address; @@ -1995,14 +2572,24 @@ uc_err uc_mem_unmap(struct uc_struct *uc, uint64_t address, uint64_t size) // if we can retrieve the mapping, then no splitting took place // so unmap here - mr = uc->memory_mapping(uc, addr); - if (mr != NULL) { - uc->memory_unmap(uc, mr); + { + UcMapping *mapping = mapped_block_at(uc, addr); + + if (mapping != NULL) { + uc->memory_unmap(uc, mapping); + mem_map_remove(uc, mapping); + mapping_reclaim(uc, mapping); + } } count += len; addr += len; } + if (uc->nested_level != 0 && pc >= address && pc - address < size) { + uc->quit_request = true; + uc_emu_stop(uc); + } + restore_jit_state(uc); return UC_ERR_OK; } @@ -2011,11 +2598,17 @@ UNICORN_EXPORT uc_err uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback, void *user_data, uint64_t begin, uint64_t end, ...) { + const unsigned int valid_types = (1U << UC_HOOK_MAX) - 1; int ret = UC_ERR_OK; int i = 0; UC_INIT(uc); + if (type == 0 || ((unsigned int)type & ~valid_types) != 0) { + restore_jit_state(uc); + return UC_ERR_HOOK; + } + struct hook *hook = calloc(1, sizeof(struct hook)); if (hook == NULL) { restore_jit_state(uc); @@ -2064,6 +2657,14 @@ uc_err uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback, } uc->hooks_count[UC_HOOK_INSN_IDX]++; + hook_dispatch_cache_invalidate(uc, UC_HOOK_INSN_IDX); + if (uc->arch == UC_ARCH_ARM64) { + request_tb_flush(uc); + if (uc->nested_level) { + uc->quit_request = true; + break_translation_loop(uc); + } + } restore_jit_state(uc); return UC_ERR_OK; } @@ -2099,15 +2700,12 @@ uc_err uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback, } uc->hooks_count[UC_HOOK_TCG_OPCODE_IDX]++; + hook_dispatch_cache_invalidate(uc, UC_HOOK_TCG_OPCODE_IDX); return UC_ERR_OK; } - if (type & UC_HOOK_CODE || type & UC_HOOK_BLOCK) { - if (end <= begin) { - uc->tb_flush(uc); - } else { - uc->uc_invalidate_tb(uc, begin, end-begin); - } + if (type & (UC_HOOK_CODE | UC_HOOK_BLOCK | UC_HOOK_MEM_FETCH)) { + hook_invalidate_range(uc, begin, end); if (uc->nested_level) { uc->quit_request = true; break_translation_loop(uc); @@ -2132,11 +2730,19 @@ uc_err uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback, } } uc->hooks_count[i]++; + hook_dispatch_cache_invalidate(uc, i); + if (i == UC_HOOK_EDGE_GENERATED_IDX) { + uc->last_tb_valid = false; + } } } i++; } + if (hook->refs != 0 && (type & UC_HOOK_MEM_FAST_PATH)) { + uc->tcg_flush_tlb(uc); + } + // we didn't use the hook // TODO: return an error? if (hook->refs == 0) { @@ -2151,6 +2757,9 @@ UNICORN_EXPORT uc_err uc_hook_del(uc_engine *uc, uc_hook hh) { int i; + bool flush_tlb = false; + bool flush_tb = false; + bool exit_direct_hook = false; struct hook *hook = (struct hook *)hh; UC_INIT(uc); @@ -2162,17 +2771,48 @@ uc_err uc_hook_del(uc_engine *uc, uc_hook hh) // and store the type mask in the hook pointer. for (i = 0; i < UC_HOOK_MAX; i++) { if (list_exists(&uc->hook[i], (void *)hook)) { - if (hook->type & UC_HOOK_CODE || hook->type & UC_HOOK_BLOCK) { + if (hook->type & UC_HOOK_MEM_FAST_PATH) { + flush_tlb = true; + } + if (i == UC_HOOK_INSN_IDX && uc->arch == UC_ARCH_ARM64) { + flush_tb = true; + } + if (hook->type & + (UC_HOOK_CODE | UC_HOOK_BLOCK | UC_HOOK_MEM_FETCH)) { g_hash_table_foreach(hook->hooked_regions, hook_invalidate_region, uc); + if ((i == UC_HOOK_CODE_IDX || i == UC_HOOK_BLOCK_IDX) && + uc->hooks_count[i] == 1 && + (uc_hook)hook != uc->count_hook) { + exit_direct_hook = true; + } } g_hash_table_remove_all(hook->hooked_regions); hook->to_delete = true; uc->hooks_count[i]--; + hook_dispatch_cache_invalidate(uc, i); + if (i == UC_HOOK_EDGE_GENERATED_IDX) { + uc->last_tb_valid = false; + } hook_append(&uc->hooks_to_del, hook); } } + if (flush_tlb) { + uc->tcg_flush_tlb(uc); + } + if (flush_tb) { + request_tb_flush(uc); + if (uc->nested_level) { + uc->quit_request = true; + break_translation_loop(uc); + } + } + if (exit_direct_hook && uc->nested_level) { + uc->quit_request = true; + break_translation_loop(uc); + } + restore_jit_state(uc); return UC_ERR_OK; } @@ -2181,15 +2821,11 @@ UNICORN_EXPORT uc_err uc_hook_set_user_data(uc_engine *uc, uc_hook hh, void *user_data) { struct hook *hook = (struct hook *)hh; - if (hook->type == UC_HOOK_BLOCK || hook->type == UC_HOOK_CODE) { + if (hook->type & (UC_HOOK_BLOCK | UC_HOOK_CODE | UC_HOOK_MEM_FETCH)) { if (uc->nested_level) { return UC_ERR_ARG; } - if (hook->end < hook->begin) { - uc->tb_flush(uc); - } else { - uc->uc_invalidate_tb(uc, hook->begin, hook->end - hook->begin); - } + hook_invalidate_range(uc, hook->begin, hook->end); } hook->user_data = user_data; return UC_ERR_OK; @@ -2206,7 +2842,7 @@ void helper_uc_traceopcode(struct hook *hook, uint64_t arg1, uint64_t arg2, { struct uc_struct *uc = handle; - if (unlikely(uc->stop_request)) { + if (unlikely(uc_stop_requested(uc))) { return; } @@ -2224,7 +2860,7 @@ void helper_uc_traceopcode(struct hook *hook, uint64_t arg1, uint64_t arg2, JIT_CALLBACK_GUARD(((uc_hook_tcg_op_2)hook->callback)( uc, address, arg1, arg2, size, hook->user_data)); - if (unlikely(uc->stop_request)) { + if (unlikely(uc_stop_requested(uc))) { return; } } @@ -2256,12 +2892,32 @@ void helper_uc_tracecode(int32_t size, uc_hook_idx index, void *handle, // } // the last callback may already asked to stop emulation - if (uc->stop_request && !not_allow_stop) { + if (uc_stop_requested(uc) && !not_allow_stop) { return; - } else if (not_allow_stop && uc->stop_request) { + } else if (not_allow_stop && uc_stop_requested(uc)) { revert_uc_emu_stop(uc); } + if (index == UC_HOOK_MEM_FETCH_IDX) { + for (cur = uc->hook[index].head; + cur != NULL && (hook = (struct hook *)cur->data); + cur = cur->next) { + if (hook->to_delete) { + continue; + } + if (HOOK_BOUND_CHECK(hook, (uint64_t)address)) { + JIT_CALLBACK_GUARD(((uc_cb_hookmem_t)hook->callback)( + uc, UC_MEM_FETCH, address, size, 0, hook->user_data)); + } + if (not_allow_stop && uc_stop_requested(uc)) { + revert_uc_emu_stop(uc); + } else if (!not_allow_stop && uc_stop_requested(uc)) { + return; + } + } + return; + } + for (cur = uc->hook[index].head; cur != NULL && (hook = (struct hook *)cur->data); cur = cur->next) { if (hook->to_delete) { @@ -2291,9 +2947,9 @@ void helper_uc_tracecode(int32_t size, uc_hook_idx index, void *handle, // normally. No check_exit_request is generated and the hooks are // triggered normally. In other words, the whole IT block is treated // as a single instruction. - if (not_allow_stop && uc->stop_request) { + if (not_allow_stop && uc_stop_requested(uc)) { revert_uc_emu_stop(uc); - } else if (!not_allow_stop && uc->stop_request) { + } else if (!not_allow_stop && uc_stop_requested(uc)) { break; } } @@ -2319,8 +2975,9 @@ uc_err uc_mem_regions(uc_engine *uc, uc_mem_region **regions, uint32_t *count) } for (i = 0; i < *count; i++) { - r[i].begin = uc->mapped_blocks[i]->addr; - r[i].end = uc->mapped_blocks[i]->end - 1; + r[i].begin = uc->mapped_blocks[i]->begin; + r[i].end = + uc->mapped_blocks[i]->begin + uc->mapped_blocks[i]->size - 1; r[i].perms = uc->mapped_blocks[i]->perms; } @@ -2337,6 +2994,7 @@ uc_err uc_query(uc_engine *uc, uc_query_type type, size_t *result) switch (type) { default: + restore_jit_state(uc); return UC_ERR_ARG; case UC_QUERY_PAGE_SIZE: @@ -2350,14 +3008,17 @@ uc_err uc_query(uc_engine *uc, uc_query_type type, size_t *result) case UC_QUERY_MODE: #ifdef UNICORN_HAS_ARM if (uc->arch == UC_ARCH_ARM) { - return uc->query(uc, type, result); + uc_err ret = uc->query(uc, type, result); + + restore_jit_state(uc); + return ret; } #endif *result = uc->mode; break; case UC_QUERY_TIMEOUT: - *result = uc->timed_out; + *result = emu_atomic_read(&uc->timed_out); break; } @@ -2368,17 +3029,32 @@ uc_err uc_query(uc_engine *uc, uc_query_type type, size_t *result) UNICORN_EXPORT uc_err uc_context_alloc(uc_engine *uc, uc_context **context) { + /* QEMU target CPU states containing vector registers require alignment. */ + const size_t alignment = 16; struct uc_context **_context = context; size_t size = uc_context_size(uc); + UcContextAllocation *allocation; + uintptr_t data_address; UC_INIT(uc); - *_context = g_malloc(size); - if (*_context) { + allocation = g_malloc(sizeof(*allocation) + size + alignment - 1); + if (allocation) { + data_address = ((uintptr_t)(allocation + 1) + + sizeof(uc_context) + alignment - 1) & + ~(uintptr_t)(alignment - 1); + *_context = (uc_context *)(data_address - sizeof(uc_context)); + memset(*_context, 0, size); (*_context)->context_size = size - sizeof(uc_context); (*_context)->arch = uc->arch; (*_context)->mode = uc->mode; - (*_context)->fv = NULL; + allocation->capacity = (*_context)->context_size; + allocation->context = *_context; + while (emu_atomic_cmpxchg(&context_allocations_lock, 0, 1) != 0) { + } + allocation->next = context_allocations; + context_allocations = allocation; + emu_atomic_set(&context_allocations_lock, 0); restore_jit_state(uc); return UC_ERR_OK; } else { @@ -2394,18 +3070,238 @@ uc_err uc_free(void *mem) return UC_ERR_OK; } +static size_t context_data_size(uc_engine *uc) +{ + if (!uc->context_size) { + return uc->cpu_context_size; + } + return uc->context_size(uc); +} + +static bool context_matches_arch_mode(uc_engine *uc, + const uc_context *context) +{ + return context && context->arch == uc->arch && context->mode == uc->mode; +} + +static bool context_allocation_capacity(const uc_context *context, + size_t *capacity) +{ + UcContextAllocation *allocation = NULL; + + while (emu_atomic_cmpxchg(&context_allocations_lock, 0, 1) != 0) { + } + for (allocation = context_allocations; allocation; + allocation = allocation->next) { + if (allocation->context == context) { + *capacity = allocation->capacity; + break; + } + } + emu_atomic_set(&context_allocations_lock, 0); + return allocation != NULL; +} + UNICORN_EXPORT size_t uc_context_size(uc_engine *uc) { + size_t size; + UC_INIT(uc); + size = sizeof(uc_context) + context_data_size(uc); restore_jit_state(uc); - if (!uc->context_size) { - // return the total size of struct uc_context - return sizeof(uc_context) + uc->cpu_context_size; + return size; +} + +static void context_memory_register(uc_engine *uc, uc_context *context) +{ + assert(!context->memory_owner); + assert(!context->memory_prev); + assert(!context->memory_next); + + context->memory_owner = uc; + context->memory_next = uc->memory_contexts; + if (context->memory_next) { + context->memory_next->memory_prev = context; + } + uc->memory_contexts = context; + uc->memory_context_count++; +} + +static void context_memory_unregister(uc_context *context) +{ + uc_engine *uc = context->memory_owner; + + assert(uc); + assert(uc->memory_context_count != 0); + if (context->memory_prev) { + context->memory_prev->memory_next = context->memory_next; } else { - return sizeof(uc_context) + uc->context_size(uc); + assert(uc->memory_contexts == context); + uc->memory_contexts = context->memory_next; + } + if (context->memory_next) { + context->memory_next->memory_prev = context->memory_prev; + } + context->memory_owner = NULL; + context->memory_prev = NULL; + context->memory_next = NULL; + uc->memory_context_count--; +} + +static void context_memory_clear(uc_context *context, bool reclaim, + bool normalize) +{ + uc_engine *uc = context->memory_owner; + uint32_t i; + + if (uc) { + context_memory_unregister(context); + } + if (context->fv) { + g_free(context->fv->ranges); + g_free(context->fv); + } + + if (uc) { + for (i = 0; i < context->memory_region_count; i++) { + MemoryRegion *region = context->memory_regions[i]; + + assert(region->context_refs != 0); + region->context_refs--; + } + for (i = 0; i < context->mapping_count; i++) { + UcMapping *mapping = context->mappings[i].mapping; + + assert(mapping->context_refs != 0); + mapping->context_refs--; + } + if (reclaim) { + if (normalize && uc->memory_context_count == 0) { + mapping_normalize_active(uc); + } + mapping_reclaim_inactive(uc); + if (uc->memory_context_count == 0) { + uc->tcg_flush_tlb(uc); + } + } + } + + g_free(context->mappings); + g_free(context->memory_regions); + context->fv = NULL; + context->mappings = NULL; + context->memory_regions = NULL; + context->mapping_count = 0; + context->memory_region_count = 0; +} + +static bool context_mapping_refs_available(uc_engine *uc) +{ + uint32_t mapping_index; + + for (mapping_index = 0; mapping_index < uc->mapped_block_count; + mapping_index++) { + UcMapping *mapping = uc->mapped_blocks[mapping_index]; + MemoryRegion *region; + + if (mapping->context_refs == UINT32_MAX) { + return false; + } + if (mapping->root->terminates) { + if (mapping->root->context_refs == UINT32_MAX) { + return false; + } + } else { + QTAILQ_FOREACH(region, &mapping->root->subregions, subregions_link) + { + if (region->context_refs == UINT32_MAX) { + return false; + } + } + } + } + return true; +} + +static bool context_capture_mappings(uc_engine *uc, + UcContextMapping **mappings_out, + MemoryRegion ***regions_out, + uint32_t *region_count_out) +{ + UcContextMapping *mappings = NULL; + MemoryRegion **regions = NULL; + uint64_t region_count = 0; + uint32_t mapping_index; + uint32_t region_index = 0; + + for (mapping_index = 0; mapping_index < uc->mapped_block_count; + mapping_index++) { + UcMapping *mapping = uc->mapped_blocks[mapping_index]; + MemoryRegion *region; + + if (mapping->root->terminates) { + region_count++; + } else { + QTAILQ_FOREACH(region, &mapping->root->subregions, subregions_link) + { + region_count++; + } + } + if (region_count > UINT32_MAX) { + return false; + } + } + + if (uc->mapped_block_count != 0) { + if (test_alloc_should_fail(uc, + UC_TEST_ALLOC_FAIL_CONTEXT_MAPPINGS)) { + return false; + } + mappings = g_try_new(UcContextMapping, uc->mapped_block_count); + if (!mappings) { + return false; + } + } + if (region_count != 0) { + if (test_alloc_should_fail(uc, + UC_TEST_ALLOC_FAIL_CONTEXT_REGIONS)) { + g_free(mappings); + return false; + } + regions = g_try_new(MemoryRegion *, (uint32_t)region_count); + if (!regions) { + g_free(mappings); + return false; + } + } + + for (mapping_index = 0; mapping_index < uc->mapped_block_count; + mapping_index++) { + UcMapping *mapping = uc->mapped_blocks[mapping_index]; + UcContextMapping *saved = &mappings[mapping_index]; + MemoryRegion *region; + + saved->mapping = mapping; + saved->first_region = region_index; + if (mapping->root->terminates) { + regions[region_index++] = mapping->root; + } else { + QTAILQ_FOREACH(region, &mapping->root->subregions, subregions_link) + { + regions[region_index++] = region; + } + } + saved->region_count = region_index - saved->first_region; + assert(saved->region_count != 0); } + + assert(region_index == region_count); + *mappings_out = mappings; + *regions_out = regions; + *region_count_out = region_index; + return true; } UNICORN_EXPORT @@ -2413,34 +3309,101 @@ uc_err uc_context_save(uc_engine *uc, uc_context *context) { UC_INIT(uc); uc_err ret = UC_ERR_OK; + size_t data_size = context_data_size(uc); + size_t capacity = 0; + bool allocated; + + if (!context) { + restore_jit_state(uc); + return UC_ERR_ARG; + } + allocated = context_allocation_capacity(context, &capacity); + if (allocated) { + if (!context_matches_arch_mode(uc, context) || capacity < data_size) { + restore_jit_state(uc); + return UC_ERR_ARG; + } + } else { + if (uc->context_content & UC_CTL_CONTEXT_MEMORY) { + restore_jit_state(uc); + return UC_ERR_ARG; + } + memset(context, 0, sizeof(*context)); + context->arch = uc->arch; + context->mode = uc->mode; + } + if ((uc->context_content & UC_CTL_CONTEXT_MEMORY) && + context->memory_owner && context->memory_owner != uc) { + restore_jit_state(uc); + return UC_ERR_ARG; + } + context->context_size = data_size; if (uc->context_content & UC_CTL_CONTEXT_MEMORY) { - if (!context->fv) { - context->fv = g_malloc0(sizeof(*context->fv)); + UcContextMapping *mappings = NULL; + MemoryRegion **regions = NULL; + uint32_t region_count = 0; + FlatView *fv = NULL; + + if (!test_alloc_should_fail(uc, UC_TEST_ALLOC_FAIL_CONTEXT_VIEW)) { + fv = g_try_malloc0(sizeof(*fv)); } - if (!context->fv) { - return UC_ERR_NOMEM; + + if (uc->memory_context_count == UINT32_MAX) { + g_free(fv); + restore_jit_state(uc); + return UC_ERR_RESOURCE; } - if (!uc->flatview_copy(uc, context->fv, - uc->address_space_memory.current_map, false)) { + if (!context_mapping_refs_available(uc)) { + g_free(fv); + restore_jit_state(uc); + return UC_ERR_RESOURCE; + } + if (!fv || + !context_capture_mappings(uc, &mappings, ®ions, ®ion_count) || + !uc->flatview_copy(uc, fv, uc->address_space_memory.current_map, + false)) { + if (fv) { + g_free(fv->ranges); + } + g_free(fv); + g_free(mappings); + g_free(regions); restore_jit_state(uc); return UC_ERR_NOMEM; } ret = uc_snapshot(uc); if (ret != UC_ERR_OK) { + g_free(fv->ranges); + g_free(fv); + g_free(mappings); + g_free(regions); restore_jit_state(uc); return ret; } + + context_memory_clear(context, true, false); + context->fv = fv; + context->mappings = mappings; + context->memory_regions = regions; + context->mapping_count = uc->mapped_block_count; + context->memory_region_count = region_count; + for (uint32_t i = 0; i < context->mapping_count; i++) { + context->mappings[i].mapping->context_refs++; + } + for (uint32_t i = 0; i < context->memory_region_count; i++) { + context->memory_regions[i]->context_refs++; + } + context_memory_register(uc, context); context->ramblock_freed = uc->ram_list.freed; context->last_block = uc->ram_list.last_block; + context->snapshot_level = uc->snapshot_level; uc->tcg_flush_tlb(uc); } - context->snapshot_level = uc->snapshot_level; - if (uc->context_content & UC_CTL_CONTEXT_CPU) { if (!uc->context_save) { - memcpy(context->data, uc->cpu->env_ptr, context->context_size); + memcpy(context->data, uc->cpu->env_ptr, data_size); restore_jit_state(uc); return UC_ERR_OK; } else { @@ -2697,46 +3660,143 @@ uc_err uc_context_restore(uc_engine *uc, uc_context *context) { UC_INIT(uc); uc_err ret; + size_t capacity = 0; + + if (!context_matches_arch_mode(uc, context)) { + restore_jit_state(uc); + return UC_ERR_ARG; + } + if (context_allocation_capacity(context, &capacity) && + context->context_size > capacity) { + restore_jit_state(uc); + return UC_ERR_ARG; + } + + if (uc->context_content & UC_CTL_CONTEXT_CPU) { + if (uc->context_validate) { + ret = uc->context_validate(uc, context); + if (ret != UC_ERR_OK) { + restore_jit_state(uc); + return ret; + } + } else if (context->context_size < context_data_size(uc)) { + restore_jit_state(uc); + return UC_ERR_ARG; + } + } if (uc->context_content & UC_CTL_CONTEXT_MEMORY) { - uc->snapshot_level = context->snapshot_level; - if (!uc->flatview_copy(uc, uc->address_space_memory.current_map, - context->fv, true)) { + uint64_t active_tb_mask; + FlatView *restore_view; + uint32_t i; + bool copied; + + if (!context->fv || context->memory_owner != uc) { + restore_jit_state(uc); + return UC_ERR_ARG; + } + if (test_alloc_should_fail(uc, UC_TEST_ALLOC_FAIL_RESTORE_VIEW)) { + restore_view = NULL; + } else { + restore_view = g_try_malloc0(sizeof(*restore_view)); + } + if (!restore_view) { + restore_jit_state(uc); return UC_ERR_NOMEM; } - ret = uc_restore_latest_snapshot(uc); + restore_view->ref = 1; + ret = uc_restore_snapshot_preflight(uc, context, restore_view); if (ret != UC_ERR_OK) { + g_free(restore_view->ranges); + g_free(restore_view); restore_jit_state(uc); return ret; } - uc_snapshot(uc); - uc->ram_list.freed = context->ramblock_freed; - uc->ram_list.last_block = context->last_block; + + active_tb_mask = all_active_tb_mask(uc); + mark_active_tbs_for_exit(uc, active_tb_mask); + request_tb_flush(uc); + + while (uc->mapped_block_count != 0) { + UcMapping *mapping = uc->mapped_blocks[uc->mapped_block_count - 1]; + + uc->memory_moveout(uc, mapping, false); + mem_map_remove(uc, mapping); + } + + for (i = 0; i < context->mapping_count; i++) { + UcContextMapping *saved = &context->mappings[i]; + UcMapping *mapping = saved->mapping; + + uc->memory_restore_topology( + uc, mapping, &context->memory_regions[saved->first_region], + saved->region_count, false); + uc->memory_movein(uc, mapping, false); + mem_map_activate(uc, mapping); + } + + copied = uc->flatview_copy(uc, restore_view, context->fv, false); + assert(copied); + (void)copied; + uc->address_space_restore_flatview(&uc->address_space_memory, + restore_view); + mapping_reclaim_inactive(uc); + + if (uc->nested_level != 0) { + uc->quit_request = true; + break_translation_loop(uc); + } uc->tcg_flush_tlb(uc); } if (uc->context_content & UC_CTL_CONTEXT_CPU) { if (!uc->context_restore) { - memcpy(uc->cpu->env_ptr, context->data, context->context_size); + size_t data_size = context_data_size(uc); + + memcpy(uc->cpu->env_ptr, context->data, data_size); + if (uc->nested_level != 0) { + uc_request_pc_change(uc); + } restore_jit_state(uc); return UC_ERR_OK; } else { ret = uc->context_restore(uc, context); + if (ret == UC_ERR_OK && uc->nested_level != 0) { + uc_request_pc_change(uc); + } restore_jit_state(uc); return ret; } } + restore_jit_state(uc); return UC_ERR_OK; } UNICORN_EXPORT uc_err uc_context_free(uc_context *context) { - if (context->fv) { - free(context->fv->ranges); - g_free(context->fv); + UcContextAllocation *allocation = NULL; + UcContextAllocation **link; + + if (!context) { + return UC_ERR_ARG; + } + while (emu_atomic_cmpxchg(&context_allocations_lock, 0, 1) != 0) { + } + for (link = &context_allocations; *link; link = &(*link)->next) { + if ((*link)->context == context) { + allocation = *link; + *link = allocation->next; + break; + } + } + emu_atomic_set(&context_allocations_lock, 0); + if (!allocation) { + return UC_ERR_ARG; } - return uc_free(context); + + context_memory_clear(context, true, true); + return uc_free(allocation); } typedef struct _uc_ctl_exit_request { @@ -2820,7 +3880,7 @@ uc_err uc_ctl(uc_engine *uc, uc_control_type control, ...) break; } - if ((page_size & (page_size - 1))) { + if (page_size == 0 || (page_size & (page_size - 1))) { err = UC_ERR_ARG; break; } @@ -2842,7 +3902,11 @@ uc_err uc_ctl(uc_engine *uc, uc_control_type control, ...) case UC_CTL_UC_USE_EXITS: { if (rw == UC_CTL_IO_WRITE) { int use_exits = va_arg(args, int); + uc->use_exits = use_exits; + if (!use_exits && uc->ctl_exits != NULL) { + g_tree_remove_all(uc->ctl_exits); + } } else { err = UC_ERR_ARG; } @@ -2909,7 +3973,7 @@ uc_err uc_ctl(uc_engine *uc, uc_control_type control, ...) int *model = va_arg(args, int *); *model = uc->cpu_model; - save_jit_state(uc); + restore_jit_state(uc); } else { int model = va_arg(args, int); @@ -3050,7 +4114,7 @@ uc_err uc_ctl(uc_engine *uc, uc_control_type control, ...) UC_INIT(uc); if (rw == UC_CTL_IO_WRITE) { - uc->tb_flush(uc); + request_tb_flush(uc); } else { err = UC_ERR_ARG; } @@ -3078,6 +4142,9 @@ uc_err uc_ctl(uc_engine *uc, uc_control_type control, ...) if (rw == UC_CTL_IO_WRITE) { int mode = va_arg(args, int); err = uc->set_tlb(uc, mode); + if (err == UC_ERR_OK) { + uc->tcg_flush_tlb(uc); + } } else { err = UC_ERR_ARG; } @@ -3240,48 +4307,68 @@ static uc_err uc_snapshot(struct uc_struct *uc) return UC_ERR_OK; } -static uc_err uc_restore_latest_snapshot(struct uc_struct *uc) +static uc_err uc_restore_snapshot_preflight(uc_engine *uc, + const uc_context *context, + FlatView *restore_view) { - MemoryRegion *subregion, *subregion_next, *mr, *initial_mr; - int level; + uint32_t consumed_regions = 0; + UcMapping *previous = NULL; + uint32_t i; - QTAILQ_FOREACH_SAFE(subregion, &uc->system_memory->subregions, - subregions_link, subregion_next) - { - uc->memory_filter_subregions(subregion, uc->snapshot_level); - if (subregion->priority >= uc->snapshot_level || - (!subregion->terminates && QTAILQ_EMPTY(&subregion->subregions))) { - uc->memory_unmap(uc, subregion); - } + if (context->memory_owner != uc || context->snapshot_level <= 0 || + (context->mapping_count != 0 && !context->mappings) || + (context->memory_region_count != 0 && !context->memory_regions) || + (context->fv->nr != 0 && !context->fv->ranges)) { + return UC_ERR_ARG; } - for (size_t i = uc->unmapped_regions->len; i-- > 0;) { - mr = g_array_index(uc->unmapped_regions, MemoryRegion *, i); - // same dirty hack as in memory_moveout see qemu/softmmu/memory.c - initial_mr = QTAILQ_FIRST(&mr->subregions); - if (!initial_mr) { - initial_mr = mr; - } - /* same dirty hack as in memory_moveout see qemu/softmmu/memory.c */ - level = (intptr_t)mr->container; - mr->container = NULL; + for (i = 0; i < context->mapping_count; i++) { + const UcContextMapping *saved = &context->mappings[i]; + UcMapping *mapping = saved->mapping; + uint32_t j; - if (level < uc->snapshot_level) { - break; + if (!mapping || mapping->owner != uc || !mapping->root || + mapping->size == 0 || + mapping->begin > UINT64_MAX - (mapping->size - 1) || + saved->region_count == 0 || + saved->first_region != consumed_regions || + saved->region_count > + context->memory_region_count - consumed_regions) { + return UC_ERR_ARG; } - if (memory_overlap(uc, mr->addr, int128_get64(mr->size))) { + if (previous && + (mapping->begin < previous->begin || + ranges_overlap(previous->begin, previous->size, mapping->begin, + mapping->size))) { return UC_ERR_MAP; } - uc->memory_movein(uc, mr); - uc->memory_filter_subregions(mr, uc->snapshot_level); - if (initial_mr != mr && QTAILQ_EMPTY(&mr->subregions)) { - uc->memory_unmap(uc, subregion); + previous = mapping; + + if (mapping->root->terminates && + (saved->region_count != 1 || + context->memory_regions[saved->first_region] != mapping->root)) { + return UC_ERR_ARG; + } + + for (j = 0; j < saved->region_count; j++) { + MemoryRegion *region = + context->memory_regions[saved->first_region + j]; + + if (!region || region->uc_mapping != mapping || + (!mapping->root->terminates && region == mapping->root)) { + return UC_ERR_ARG; + } } - mem_map(uc, initial_mr); - g_array_remove_range(uc->unmapped_regions, i, 1); + consumed_regions += saved->region_count; } - uc->snapshot_level--; + if (consumed_regions != context->memory_region_count) { + return UC_ERR_ARG; + } + if (!mem_map_reserve(uc, context->mapping_count) || + !uc->flatview_reserve(restore_view, context->fv->nr)) { + return UC_ERR_NOMEM; + } return UC_ERR_OK; }