From 6fbbf3089af67643c2e8d6496ba84976b35461a1 Mon Sep 17 00:00:00 2001 From: "@Antelox" Date: Thu, 17 Oct 2024 13:35:42 +0200 Subject: [PATCH] Python binding setup refactoring + cibuildwheel workflow (#2026) * Python bindings: Make the test scripts handy for pytest * Python bindings: Update MANIFEST.in with new paths * Update .gitignore to exclude PyCharm-related files/folders * Python bindings: Update CMakeLists.txt in order to set CMAKE_OSX_ARCHITECTURES var * Python bindings: - Moved project package settings to the new TOML format - Refactored setup.py to cleanup/improve the code and make it ready for cibuildwheel - Updated README.md with the package long description part - Removed setup.cfg since universal wheel building will be deprecated soon * Python bindings: - Replaced old PyPI-publishing.yml workflow with brand-new one based on cibuildwheel - Removed old building scripts * Replaced macos-12 runner with macos-13 since it will be removed soon * Python bindings: Specify SYSTEM_VERSION_COMPAT=0 env var for macos-13 x86_64 runner as per cibuildwheel warning message * Python bindings: Enable i686 for debugging * Python bindings: Enable DEBUG flag according to the presence of tag release * Python bindings: Added matrix to cover i686 manylinux/musllinux builds * Python bindings: - Replaced macos-14 runner with macos-latest - Bumped cibuildwheel GitHub action to 2.21.3 version * Python bindings: - Adapt test_uc_ctl_tb_cache test to the recent changes - Fixed typos - PEP8 fixes * GitHub Action Workflow: Introduce BUILD_TYPE env var to select build type according to the presence of tag release --------- Co-authored-by: mio --- .github/workflows/PyPI-publishing.yml | 167 --- .github/workflows/build-uc2.yml | 998 +++++++++--------- .github/workflows/build-wheels-publish.yml | 360 +++++++ .gitignore | 8 +- CMakeLists.txt | 18 + bindings/python/MANIFEST.in | 9 +- bindings/python/README.md | 18 + bindings/python/build_wheel.sh | 15 - bindings/python/musl_wheel.sh | 9 - bindings/python/pyproject.toml | 40 + bindings/python/sample_all.sh | 31 - bindings/python/setup.cfg | 2 - bindings/python/setup.py | 180 +--- .../{sample_arm.py => tests/test_arm.py} | 23 +- .../{sample_arm64.py => tests/test_arm64.py} | 15 +- .../test_arm64eb.py} | 11 +- .../{sample_armeb.py => tests/test_armeb.py} | 21 +- .../{sample_ctl.py => tests/test_ctl.py} | 25 +- .../{sample_m68k.py => tests/test_m68k.py} | 9 +- .../{sample_mips.py => tests/test_mips.py} | 15 +- .../test_network_auditing.py} | 6 +- .../{sample_ppc.py => tests/test_ppc.py} | 11 +- .../{sample_riscv.py => tests/test_riscv.py} | 13 +- .../{sample_s390x.py => tests/test_s390x.py} | 7 +- .../{shellcode.py => tests/test_shellcode.py} | 51 +- .../{sample_sparc.py => tests/test_sparc.py} | 13 +- .../test_tricore.py} | 18 +- .../{sample_x86.py => tests/test_x86.py} | 152 +-- 28 files changed, 1193 insertions(+), 1052 deletions(-) delete mode 100644 .github/workflows/PyPI-publishing.yml create mode 100644 .github/workflows/build-wheels-publish.yml delete mode 100755 bindings/python/build_wheel.sh delete mode 100644 bindings/python/musl_wheel.sh create mode 100644 bindings/python/pyproject.toml delete mode 100755 bindings/python/sample_all.sh delete mode 100644 bindings/python/setup.cfg rename bindings/python/{sample_arm.py => tests/test_arm.py} (89%) rename bindings/python/{sample_arm64.py => tests/test_arm64.py} (92%) rename bindings/python/{sample_arm64eb.py => tests/test_arm64eb.py} (91%) rename bindings/python/{sample_armeb.py => tests/test_armeb.py} (88%) rename bindings/python/{sample_ctl.py => tests/test_ctl.py} (89%) rename bindings/python/{sample_m68k.py => tests/test_m68k.py} (96%) rename bindings/python/{sample_mips.py => tests/test_mips.py} (91%) rename bindings/python/{sample_network_auditing.py => tests/test_network_auditing.py} (99%) rename bindings/python/{sample_ppc.py => tests/test_ppc.py} (93%) rename bindings/python/{sample_riscv.py => tests/test_riscv.py} (92%) rename bindings/python/{sample_s390x.py => tests/test_s390x.py} (96%) rename bindings/python/{shellcode.py => tests/test_shellcode.py} (82%) rename bindings/python/{sample_sparc.py => tests/test_sparc.py} (88%) rename bindings/python/{sample_tricore.py => tests/test_tricore.py} (89%) rename bindings/python/{sample_x86.py => tests/test_x86.py} (85%) diff --git a/.github/workflows/PyPI-publishing.yml b/.github/workflows/PyPI-publishing.yml deleted file mode 100644 index d4ad442849..0000000000 --- a/.github/workflows/PyPI-publishing.yml +++ /dev/null @@ -1,167 +0,0 @@ -name: PyPI 📦 Distribution - -on: - push: - paths-ignore: - - ".gitignore" - - "docs/**" - - "README" - - "CREDITS.TXT" - - "COPYING_GLIB" - - "COPYING.LGPL2" - - "AUTHORS.TXT" - - "CHANGELOG" - - "COPYING" - pull_request: - -jobs: - build: - runs-on: ${{ matrix.config.os }} - name: ${{ matrix.config.name }} - strategy: - fail-fast: false - matrix: - config: - - { - os: windows-2019, - arch: x64, - python-ver: '3.8', - name: 'win_amd64' - } - - { - os: windows-2019, - arch: x32, - python-ver: '3.8', - name: 'win32' - } - - { - os: ubuntu-latest, - arch: x64, - python-ver: '3.8', - name: 'musllinux' - } - - { - os: ubuntu-latest, - arch: x64, - python-ver: '3.8', - name: 'manylinux2014_x86_64' - } - - { - os: ubuntu-latest, - arch: x32, - python-ver: '3.8', - name: 'manylinux2014_i686' - } - - { - os: ubuntu-latest, - arch: aarch64, - python-ver: '3.8', - name: 'manylinux2014_aarch64' - } - - { - os: ubuntu-latest, - arch: x64, - python-ver: '3.8', - name: 'sdist' - } - - { - os: macos-12, - arch: x86_64, - python-ver: '3.8', - name: 'macos_x86_64' - } - - { - os: macos-14, - arch: arm64, - python-ver: '3.10', - name: 'macos_arm64' - } - steps: - - uses: actions/checkout@v4 - - - name: '🛠️ Set up Python' - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.config.python-ver }} - - - name: '🛠️ Add msbuild to PATH' - if: contains(matrix.config.name, 'win') - uses: microsoft/setup-msbuild@v2 - with: - vs-version: '16.5' - - - name: '🛠️ Win MSVC 32 dev cmd setup' - if: contains(matrix.config.name, 'win32') - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: x86 - - - name: '🛠️ Win MSVC 64 dev cmd setup' - if: contains(matrix.config.name, 'win_amd64') - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: x64 - - - name: '🛠️ Win build dependencies' - if: contains(matrix.config.name, 'win') - shell: bash - run: | - choco install ninja cmake - - - name: '🛠️ macOS dependencies' - if: contains(matrix.config.name, 'macos') - run: | - brew install ninja - - - name: '🛠️ pip dependencies' - run: | - pip install --upgrade setuptools wheel - - - name: '🚧 Build distribution' - shell: bash - run: | - if [ ${{ matrix.config.name }} == 'win32' ]; then - cd bindings/python && python setup.py build -p win32 sdist bdist_wheel -p win32 - rm dist/*.tar.gz - elif [ ${{ matrix.config.name }} == 'manylinux2014_i686' ]; then - docker run --rm -v `pwd`/:/work dockcross/manylinux2014-x86 > ./dockcross - chmod +x ./dockcross - ./dockcross bindings/python/build_wheel.sh - elif [ ${{ matrix.config.name }} == 'manylinux2014_aarch64' ]; then - docker run --rm -v `pwd`/:/work dockcross/manylinux2014-aarch64 > ./dockcross - chmod +x ./dockcross - ./dockcross bindings/python/build_wheel.sh --plat-name manylinux2014_aarch64 - elif [ ${{ matrix.config.name }} == 'manylinux2014_x86_64' ]; then - docker run --rm -v `pwd`/:/work dockcross/manylinux2014-x64 > ./dockcross - chmod +x ./dockcross - ./dockcross bindings/python/build_wheel.sh - elif [ ${{ matrix.config.name }} == 'musllinux' ]; then - docker run --rm -v `pwd`:/work -w /work python:3.7-alpine sh /work/bindings/python/musl_wheel.sh - elif [ ${{ matrix.config.name }} == 'sdist' ]; then - cd bindings/python && python setup.py sdist - elif [ ${{ matrix.config.name }} == 'macos_arm64' ]; then - cd bindings/python && _PYTHON_HOST_PLATFORM="macosx-11.0-arm64" ARCHFLAGS="-arch arm64" python setup.py bdist_wheel - else - cd bindings/python && python setup.py bdist_wheel - fi - - name: '📤 Upload artifact' - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.config.name }} - path: ${{ github.workspace }}/bindings/python/dist/* - - publish: - needs: [build] - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags') - steps: - - uses: actions/download-artifact@v4 - with: - merge-multiple: true - path: dist - - - name: '📦 Publish distribution to PyPI' - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.pypi_pass }} diff --git a/.github/workflows/build-uc2.yml b/.github/workflows/build-uc2.yml index b5daa35143..554e3ec0e9 100644 --- a/.github/workflows/build-uc2.yml +++ b/.github/workflows/build-uc2.yml @@ -1,21 +1,22 @@ name: Build UC2 -on: +on: push: paths-ignore: - ".gitignore" - - "docs/**" - - "README" - - "CREDITS.TXT" - - "COPYING_GLIB" - - "COPYING.LGPL2" - "AUTHORS.TXT" - - "CHANGELOG" - "COPYING" + - "COPYING.LGPL2" + - "COPYING_GLIB" + - "CREDITS.TXT" + - "ChangeLog" + - "README.md" + - "docs/**" pull_request: env: - CI: true + # Build Debug mode if not tag release + BUILD_TYPE: ${{ startsWith(github.ref, 'refs/tags') && 'Release' || 'Debug' }} jobs: Windows: @@ -25,230 +26,222 @@ jobs: fail-fast: false matrix: config: - - { - os: windows-2019, - arch: x64, - python-arch: x64, - python-ver: '3.8', - name: 'windows-x64 MINGW64 shared', - shared: 'yes', - mingw: MINGW64, - mingw-arch: x86_64, - artifact: 'windows_mingw64-shared.7z', - build_type: 'Debug', - archiver: '7z a', - generators: 'Ninja' - } - - { - os: windows-2019, - arch: x64, - python-arch: x64, - python-ver: '3.8', - name: 'windows-x64 MINGW64 static', - shared: 'no', - mingw: MINGW64, - mingw-arch: x86_64, - artifact: 'windows_mingw64-static.7z', - build_type: 'Debug', - archiver: '7z a', - generators: 'Ninja' - } - # - { # This fails randomly which can't be reproduced. - # os: windows-2019, - # arch: x64, - # python-arch: x64, - # python-ver: '3.8', - # name: 'windows-x64 MINGW32 shared', - # shared: "yes", - # mingw: MINGW32, - # mingw-arch: i686, - # artifact: 'windows_mingw32.7z', - # build_type: 'Debug', - # archiver: '7z a', - # generators: 'Ninja' - # } - # - { # This fails randomly which can't be reproduced. - # os: windows-2019, - # arch: x64, - # python-arch: x64, - # python-ver: '3.8', - # name: 'windows-x64 MINGW32 static', - # shared: "no", - # mingw: MINGW32, - # mingw-arch: i686, - # artifact: 'windows_mingw32.7z', - # build_type: 'Debug', - # archiver: '7z a', - # generators: 'Ninja' - # } - - { - os: windows-2019, - arch: x64, - python-arch: x64, - python-ver: '3.8', - name: 'windows-x64 MSVC 64bit shared', - msvc-arch: x64, - artifact: 'windows_msvc64_shared.7z', - shared: 'yes', - build_type: 'Debug', - archiver: '7z a', - generators: 'Visual Studio 16 2019' - } - { - os: windows-2019, - arch: x86, - python-arch: x86, - python-ver: '3.8', - name: 'windows-x86 MSVC 32bit shared', - msvc-arch: x86, - artifact: 'windows_msvc32_shared.7z', - shared: 'yes', - build_type: 'Debug', - archiver: '7z a', - generators: 'Visual Studio 16 2019' - } - - { - os: windows-2019, - arch: x64, - python-arch: x64, - python-ver: '3.8', - name: 'windows-x64 MSVC 64bit static', - msvc-arch: x64, - artifact: 'windows_msvc64_static.7z', - shared: 'no', - build_type: 'Debug', - archiver: '7z a', - generators: 'Visual Studio 16 2019' - } - - { - os: windows-2019, - arch: x86, - python-arch: x86, - python-ver: '3.8', - name: 'windows-x86 MSVC 32bit static', - msvc-arch: x86, - artifact: 'windows_msvc32_static.7z', - shared: 'no', - build_type: 'Debug', - archiver: '7z a', - generators: 'Visual Studio 16 2019' - } + os: windows-2019, + arch: x64, + python-arch: x64, + python-ver: '3.8', + name: 'windows-x64 MINGW64 shared', + shared: 'yes', + mingw: MINGW64, + mingw-arch: x86_64, + artifact: 'windows_mingw64-shared.7z', + archiver: '7z a', + generators: 'Ninja' + } + - { + os: windows-2019, + arch: x64, + python-arch: x64, + python-ver: '3.8', + name: 'windows-x64 MINGW64 static', + shared: 'no', + mingw: MINGW64, + mingw-arch: x86_64, + artifact: 'windows_mingw64-static.7z', + archiver: '7z a', + generators: 'Ninja' + } +# - { # This fails randomly which can't be reproduced. +# os: windows-2019, +# arch: x64, +# python-arch: x64, +# python-ver: '3.8', +# name: 'windows-x64 MINGW32 shared', +# shared: "yes", +# mingw: MINGW32, +# mingw-arch: i686, +# artifact: 'windows_mingw32.7z', +# archiver: '7z a', +# generators: 'Ninja' +# } +# - { # This fails randomly which can't be reproduced. +# os: windows-2019, +# arch: x64, +# python-arch: x64, +# python-ver: '3.8', +# name: 'windows-x64 MINGW32 static', +# shared: "no", +# mingw: MINGW32, +# mingw-arch: i686, +# artifact: 'windows_mingw32.7z', +# archiver: '7z a', +# generators: 'Ninja' +# } + - { + os: windows-2019, + arch: x64, + python-arch: x64, + python-ver: '3.8', + name: 'windows-x64 MSVC 64bit shared', + msvc-arch: x64, + artifact: 'windows_msvc64_shared.7z', + shared: 'yes', + archiver: '7z a', + generators: 'Visual Studio 16 2019' + } + - { + os: windows-2019, + arch: x86, + python-arch: x86, + python-ver: '3.8', + name: 'windows-x86 MSVC 32bit shared', + msvc-arch: x86, + artifact: 'windows_msvc32_shared.7z', + shared: 'yes', + archiver: '7z a', + generators: 'Visual Studio 16 2019' + } + - { + os: windows-2019, + arch: x64, + python-arch: x64, + python-ver: '3.8', + name: 'windows-x64 MSVC 64bit static', + msvc-arch: x64, + artifact: 'windows_msvc64_static.7z', + shared: 'no', + archiver: '7z a', + generators: 'Visual Studio 16 2019' + } + - { + os: windows-2019, + arch: x86, + python-arch: x86, + python-ver: '3.8', + name: 'windows-x86 MSVC 32bit static', + msvc-arch: x86, + artifact: 'windows_msvc32_static.7z', + shared: 'no', + archiver: '7z a', + generators: 'Visual Studio 16 2019' + } compiler: [ gcc ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: '🛠️ Win MINGW setup' - if: contains(matrix.config.mingw, 'MINGW') - uses: msys2/setup-msys2@v2 - with: - msystem: ${{ matrix.config.mingw }} - install: >- - git - mingw-w64-${{ matrix.config.mingw-arch }}-cmake - mingw-w64-${{ matrix.config.mingw-arch }}-ninja - mingw-w64-${{ matrix.config.mingw-arch }}-cmocka - mingw-w64-${{ matrix.config.mingw-arch }}-${{ matrix.compiler }} - mingw-w64-${{ matrix.config.mingw-arch }}-toolchain + - name: '🛠️ Win MINGW setup' + if: contains(matrix.config.mingw, 'MINGW') + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.config.mingw }} + install: >- + git + mingw-w64-${{ matrix.config.mingw-arch }}-cmake + mingw-w64-${{ matrix.config.mingw-arch }}-ninja + mingw-w64-${{ matrix.config.mingw-arch }}-cmocka + mingw-w64-${{ matrix.config.mingw-arch }}-${{ matrix.compiler }} + mingw-w64-${{ matrix.config.mingw-arch }}-toolchain - - name: '🛠️ Win MSVC 64 setup' - if: contains(matrix.config.name, 'MSVC 64') - uses: microsoft/setup-msbuild@v2 - - - name: '🛠️ Win MSVC 64 dev cmd setup' - if: contains(matrix.config.name, 'MSVC 64') - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: x64 + - name: '🛠️ Win MSVC 64 setup' + if: contains(matrix.config.name, 'MSVC 64') + uses: microsoft/setup-msbuild@v2 - - name: '🚧 Win MSVC 64 build' - if: contains(matrix.config.name, 'MSVC 64') - shell: bash - run: | - choco install ninja cmake - ninja --version - cmake --version - mkdir build - cmake \ - -S . \ - -B . \ - -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ - -G "${{ matrix.config.generators }}" \ - -DCMAKE_INSTALL_PREFIX:PATH=instdir \ - -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} - cmake --build . --config ${{ matrix.config.build_type }} - cmake --install . --strip --config ${{ matrix.config.build_type }} - ctest -VV -C ${{ matrix.config.build_type }} - mv Debug instdir + - name: '🛠️ Win MSVC 64 dev cmd setup' + if: contains(matrix.config.name, 'MSVC 64') + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 - - name: '🛠️ Win MSVC 32 setup' - if: contains(matrix.config.name, 'MSVC 32') - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: x86 + - name: '🚧 Win MSVC 64 build' + if: contains(matrix.config.name, 'MSVC 64') + shell: bash + run: | + choco install ninja cmake + ninja --version + cmake --version + mkdir build + cmake \ + -S . \ + -B . \ + -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ + -G "${{ matrix.config.generators }}" \ + -DCMAKE_INSTALL_PREFIX:PATH=instdir \ + -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} + cmake --build . --config ${{ env.BUILD_TYPE }} + cmake --install . --strip --config ${{ env.BUILD_TYPE }} + ctest -VV -C ${{ env.BUILD_TYPE }} + mv Debug instdir - - name: '🚧 Win MSVC 32 build' - if: contains(matrix.config.name, 'MSVC 32') - shell: bash - run: | - choco install ninja cmake - ninja --version - cmake --version - mkdir build - cmake \ - -S . \ - -B . \ - -A "win32" \ - -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ - -G "${{ matrix.config.generators }}" \ - -DCMAKE_INSTALL_PREFIX:PATH=instdir \ - -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} - cmake --build . --config ${{ matrix.config.build_type }} - cmake --install . --strip --config ${{ matrix.config.build_type }} - ctest -VV -C ${{ matrix.config.build_type }} - mv Debug instdir + - name: '🛠️ Win MSVC 32 setup' + if: contains(matrix.config.name, 'MSVC 32') + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x86 - - name: '🚧 Win MINGW build' - if: contains(matrix.config.mingw, 'MINGW') - shell: msys2 {0} - run: | - if [ ${{ matrix.config.mingw }} == 'MINGW32' ]; then - export CPPFLAGS=-D__USE_MINGW_ANSI_STDIO=1 - #export CC=i686-w64-mingw32-gcc - export AR=gcc-ar - export RANLIB=gcc-ranlib - export CFLAGS="-m32 -static" - export LDFLAGS="-m32" - export LDFLAGS_STATIC="-m32" - export UNICORN_QEMU_FLAGS="--cpu=i386" - fi - mkdir build - mkdir instdir - cmake \ - -S . \ - -B . \ - -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ - -G "${{ matrix.config.generators }}" \ - -DCMAKE_INSTALL_PREFIX:PATH=instdir \ - -DCMAKE_C_FLAGS:STRING="-static" \ - -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} - cmake --build . --config ${{ matrix.config.build_type }} - cmake --install . --strip - ctest -VV -C ${{ matrix.config.build_type }} + - name: '🚧 Win MSVC 32 build' + if: contains(matrix.config.name, 'MSVC 32') + shell: bash + run: | + choco install ninja cmake + ninja --version + cmake --version + mkdir build + cmake \ + -S . \ + -B . \ + -A "win32" \ + -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ + -G "${{ matrix.config.generators }}" \ + -DCMAKE_INSTALL_PREFIX:PATH=instdir \ + -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} + cmake --build . --config ${{ env.BUILD_TYPE }} + cmake --install . --strip --config ${{ env.BUILD_TYPE }} + ctest -VV -C ${{ env.BUILD_TYPE }} + mv Debug instdir + + - name: '🚧 Win MINGW build' + if: contains(matrix.config.mingw, 'MINGW') + shell: msys2 {0} + run: | + if [ ${{ matrix.config.mingw }} == 'MINGW32' ]; then + export CPPFLAGS=-D__USE_MINGW_ANSI_STDIO=1 + #export CC=i686-w64-mingw32-gcc + export AR=gcc-ar + export RANLIB=gcc-ranlib + export CFLAGS="-m32 -static" + export LDFLAGS="-m32" + export LDFLAGS_STATIC="-m32" + export UNICORN_QEMU_FLAGS="--cpu=i386" + fi + mkdir build + mkdir instdir + cmake \ + -S . \ + -B . \ + -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ + -G "${{ matrix.config.generators }}" \ + -DCMAKE_INSTALL_PREFIX:PATH=instdir \ + -DCMAKE_C_FLAGS:STRING="-static" \ + -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} + cmake --build . --config ${{ env.BUILD_TYPE }} + cmake --install . --strip + ctest -VV -C ${{ env.BUILD_TYPE }} - - name: '📦 Pack artifact' - if: always() - shell: bash - working-directory: instdir - run: | - ls -laR - ${{ matrix.config.archiver }} ../${{ matrix.config.artifact }} . ../test* + - name: '📦 Pack artifact' + if: always() + shell: bash + working-directory: instdir + run: | + ls -laR + ${{ matrix.config.archiver }} ../${{ matrix.config.artifact }} . ../test* - - name: '📤 Upload artifact' - if: always() - uses: actions/upload-artifact@v4 - with: - path: ./${{ matrix.config.artifact }} - name: ${{ matrix.config.artifact }} + - name: '📤 Upload artifact' + if: always() + uses: actions/upload-artifact@v4 + with: + path: ./${{ matrix.config.artifact }} + name: ${{ matrix.config.artifact }} Macos: runs-on: ${{ matrix.config.os }} @@ -257,169 +250,164 @@ jobs: fail-fast: false matrix: config: - - { - os: macos-12, # x64 - arch: x64, - python-arch: x64, - python-ver: '3.8', - name: 'macos-x64 cmake shared', - shared: 'yes', - artifact: 'macos-x64-cmake-shared-x64.7z', - build_type: 'Debug', - archiver: '7za a', - generators: 'Ninja' - } - - { - os: macos-12, - arch: x64, - python-arch: x64, - python-ver: '3.8', - name: 'macos-x64 cmake static', - shared: 'no', - artifact: 'macos-x64-cmake-static-x64.7z', - build_type: 'Debug', - archiver: '7za a', - generators: 'Ninja' - } - - { - os: macos-14, # arm64 - arch: arm64, - python-arch: arm64, - python-ver: '3.8', - name: 'macos-arm64 cmake shared', - shared: 'yes', - artifact: 'macos-arm64-cmake-shared-x64.7z', - build_type: 'Debug', - archiver: '7za a', - generators: 'Ninja' - } - - { - os: macos-14, - arch: arm64, - python-arch: arm64, - python-ver: '3.8', - name: 'macos-arm64 cmake static', - shared: 'no', - artifact: 'macos-arm64-cmake-static-x64.7z', - build_type: 'Debug', - archiver: '7za a', - generators: 'Ninja' - } - - { - os: macos-12, - arch: x86_64, - python-arch: x86_64, - python-ver: '3.8', - name: 'android cmake', - artifact: 'Android-x86_64.7z', - build_type: 'Debug', - archiver: '7za a', - generators: 'Ninja' - } + - { + os: macos-13, # x64 + arch: x64, + python-arch: x64, + python-ver: '3.8', + name: 'macos-x64 cmake shared', + shared: 'yes', + artifact: 'macos-x64-cmake-shared-x64.7z', + archiver: '7za a', + generators: 'Ninja' + } + - { + os: macos-13, + arch: x64, + python-arch: x64, + python-ver: '3.8', + name: 'macos-x64 cmake static', + shared: 'no', + artifact: 'macos-x64-cmake-static-x64.7z', + archiver: '7za a', + generators: 'Ninja' + } + - { + os: macos-14, # arm64 + arch: arm64, + python-arch: arm64, + python-ver: '3.8', + name: 'macos-arm64 cmake shared', + shared: 'yes', + artifact: 'macos-arm64-cmake-shared-x64.7z', + archiver: '7za a', + generators: 'Ninja' + } + - { + os: macos-14, + arch: arm64, + python-arch: arm64, + python-ver: '3.8', + name: 'macos-arm64 cmake static', + shared: 'no', + artifact: 'macos-arm64-cmake-static-x64.7z', + archiver: '7za a', + generators: 'Ninja' + } + - { + os: macos-13, + arch: x86_64, + python-arch: x86_64, + python-ver: '3.8', + name: 'android cmake', + artifact: 'Android-x86_64.7z', + archiver: '7za a', + generators: 'Ninja' + } compiler: [ gcc ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - # - name: '🛠️ Python setup' - # uses: actions/setup-python@v5 - # with: - # python-version: ${{ matrix.config.python-ver }} +# - name: '🛠️ Python setup' +# uses: actions/setup-python@v5 +# with: +# python-version: ${{ matrix.config.python-ver }} - - name: '🚧 Mac build' - if: contains(matrix.config.name, 'macos') - shell: bash - run: | - brew install ninja - ninja --version - cmake --version - mkdir build - mkdir instdir - cmake \ - -S . \ - -B . \ - -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ - -G "${{ matrix.config.generators }}" \ - -DCMAKE_INSTALL_PREFIX:PATH=instdir \ - -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} - cmake --build . --config ${{ matrix.config.build_type }} - cmake --install . --strip - ctest -VV -C ${{ matrix.config.build_type }} - - # - name: Setup tmate session - # if: ${{ failure() }} - # uses: mxschmitt/action-tmate@v3 + - name: '🚧 Mac build' + if: contains(matrix.config.name, 'macos') + shell: bash + run: | + brew install ninja + ninja --version + cmake --version + mkdir build + mkdir instdir + cmake \ + -S . \ + -B . \ + -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ + -G "${{ matrix.config.generators }}" \ + -DCMAKE_INSTALL_PREFIX:PATH=instdir \ + -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} + cmake --build . --config ${{ env.BUILD_TYPE }} + cmake --install . --strip + ctest -VV -C ${{ env.BUILD_TYPE }} + +# - name: Setup tmate session +# if: ${{ failure() }} +# uses: mxschmitt/action-tmate@v3 - - name: '🚧 Android x86_64 build' - if: contains(matrix.config.name, 'android') - shell: bash - run: | - brew install ninja - mkdir build - mkdir instdir - cmake . -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK/build/cmake/android.toolchain.cmake" \ - -DANDROID_PLATFORM=android-28 \ - -DANDROID_NDK="$ANDROID_NDK" \ - -DANDROID_ABI=${{ matrix.config.arch }} \ - -DOLP_SDK_ENABLE_TESTING=NO \ - -DOLP_SDK_BUILD_EXAMPLES=ON \ - -S . \ - -B . \ - -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ - -G "${{ matrix.config.generators }}" \ - -DCMAKE_INSTALL_PREFIX:PATH=instdir - cmake --build . --config ${{ matrix.config.build_type }} - cmake --install . --strip + - name: '🚧 Android x86_64 build' + if: contains(matrix.config.name, 'android') + shell: bash + run: | + brew install ninja + mkdir build + mkdir instdir + cmake . -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK/build/cmake/android.toolchain.cmake" \ + -DANDROID_PLATFORM=android-28 \ + -DANDROID_NDK="$ANDROID_NDK" \ + -DANDROID_ABI=${{ matrix.config.arch }} \ + -DOLP_SDK_ENABLE_TESTING=NO \ + -DOLP_SDK_BUILD_EXAMPLES=ON \ + -S . \ + -B . \ + -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ + -G "${{ matrix.config.generators }}" \ + -DCMAKE_INSTALL_PREFIX:PATH=instdir + cmake --build . --config ${{ env.BUILD_TYPE }} + cmake --install . --strip + + - name: '🚧 AVD Cache' + if: contains(matrix.config.name, 'android') + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-28 - - name: '🚧 AVD Cache' - if: contains(matrix.config.name, 'android') - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-28 - - - name: '🚧 Create x86_64 tests environment' - if: contains(matrix.config.name, 'android') && steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 28 - arch: ${{ matrix.config.arch }} - force-avd-creation: false - disable-animations: false - target: default - profile: Nexus 6 - emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -verbose -show-kernel - script: echo "Generated AVD snapshot for caching." + - name: '🚧 Create x86_64 tests environment' + if: contains(matrix.config.name, 'android') && steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 28 + arch: ${{ matrix.config.arch }} + force-avd-creation: false + disable-animations: false + target: default + profile: Nexus 6 + emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -verbose -show-kernel + script: echo "Generated AVD snapshot for caching." - - name: '🚧 Android x86_64 tests' - if: contains(matrix.config.name, 'android') - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 28 - force-avd-creation: false - disable-animations: true - arch: ${{ matrix.config.arch }} - target: default - profile: Nexus 6 - emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -verbose -show-kernel - script: bash ./adb.sh + - name: '🚧 Android x86_64 tests' + if: contains(matrix.config.name, 'android') + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 28 + force-avd-creation: false + disable-animations: true + arch: ${{ matrix.config.arch }} + target: default + profile: Nexus 6 + emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -verbose -show-kernel + script: bash ./adb.sh - - name: '📦 Pack artifact' - if: always() - shell: bash - working-directory: instdir - run: | - ls -laR - ${{ matrix.config.archiver }} ../${{ matrix.config.artifact }} . ../test* + - name: '📦 Pack artifact' + if: always() + shell: bash + working-directory: instdir + run: | + ls -laR + ${{ matrix.config.archiver }} ../${{ matrix.config.artifact }} . ../test* - - name: '📤 Upload artifact' - if: always() - uses: actions/upload-artifact@v4 - with: - path: ./${{ matrix.config.artifact }} - name: ${{ matrix.config.artifact }} + - name: '📤 Upload artifact' + if: always() + uses: actions/upload-artifact@v4 + with: + path: ./${{ matrix.config.artifact }} + name: ${{ matrix.config.artifact }} Linux: runs-on: ${{ matrix.config.os }} @@ -428,149 +416,143 @@ jobs: fail-fast: false matrix: config: - - { - os: ubuntu-latest, - arch: x64, - python-arch: x64, - python-ver: '3.8', - name: 'ubuntu-x64 cmake shared', - shared: 'yes', - artifact: 'ubuntu-cmake-shared-x64.7z', - build_type: 'Debug', - archiver: '7z a', - generators: 'Ninja' - } - - { - os: ubuntu-latest, - arch: x86, - python-arch: x86, - python-ver: '3.8', - name: 'ubuntu-x86 cmake shared', - shared: 'yes', - artifact: 'ubuntu-cmake-shared-x86.7z', - build_type: 'Debug', - archiver: '7z a', - generators: 'Ninja' - } - - { - os: ubuntu-latest, - arch: x64, - python-arch: x64, - python-ver: '3.8', - name: 'ubuntu-x64 cmake static', - shared: 'no', - artifact: 'ubuntu-cmake-static-x64.7z', - build_type: 'Debug', - archiver: '7z a', - generators: 'Ninja' - } - - { - os: ubuntu-latest, - arch: x86, - python-arch: x86, - python-ver: '3.8', - name: 'ubuntu-x86 cmake static', - shared: 'no', - artifact: 'ubuntu-cmake-static-x86.7z', - build_type: 'Debug', - archiver: '7z a', - generators: 'Ninja' - } - - { - os: ubuntu-latest, - arch: aarch64, - python-arch: aarch64, - python-ver: '3.8', - name: 'ubuntu-aarch64 cmake', - artifact: 'ubuntu-cmake-aarch64.7z', - build_type: 'Debug', - archiver: '7z a', - generators: 'Ninja', - distro: ubuntu20.04 - } - - { - os: ubuntu-latest, - arch: ppc64le, - python-arch: ppc, - python-ver: '3.8', - name: 'ubuntu-ppc64le cmake', - artifact: 'ubuntu-cmake-ppc64le.7z', - build_type: 'Debug', - archiver: '7z a', - generators: 'Ninja', - distro: ubuntu20.04 - } + - { + os: ubuntu-latest, + arch: x64, + python-arch: x64, + python-ver: '3.8', + name: 'ubuntu-x64 cmake shared', + shared: 'yes', + artifact: 'ubuntu-cmake-shared-x64.7z', + archiver: '7z a', + generators: 'Ninja' + } + - { + os: ubuntu-latest, + arch: x86, + python-arch: x86, + python-ver: '3.8', + name: 'ubuntu-x86 cmake shared', + shared: 'yes', + artifact: 'ubuntu-cmake-shared-x86.7z', + archiver: '7z a', + generators: 'Ninja' + } + - { + os: ubuntu-latest, + arch: x64, + python-arch: x64, + python-ver: '3.8', + name: 'ubuntu-x64 cmake static', + shared: 'no', + artifact: 'ubuntu-cmake-static-x64.7z', + archiver: '7z a', + generators: 'Ninja' + } + - { + os: ubuntu-latest, + arch: x86, + python-arch: x86, + python-ver: '3.8', + name: 'ubuntu-x86 cmake static', + shared: 'no', + artifact: 'ubuntu-cmake-static-x86.7z', + archiver: '7z a', + generators: 'Ninja' + } + - { + os: ubuntu-latest, + arch: aarch64, + python-arch: aarch64, + python-ver: '3.8', + name: 'ubuntu-aarch64 cmake', + artifact: 'ubuntu-cmake-aarch64.7z', + archiver: '7z a', + generators: 'Ninja', + distro: ubuntu20.04 + } + - { + os: ubuntu-latest, + arch: ppc64le, + python-arch: ppc, + python-ver: '3.8', + name: 'ubuntu-ppc64le cmake', + artifact: 'ubuntu-cmake-ppc64le.7z', + archiver: '7z a', + generators: 'Ninja', + distro: ubuntu20.04 + } compiler: [ gcc ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - # - name: '🛠️ Python setup' - # uses: actions/setup-python@v5 - # with: - # python-version: ${{ matrix.config.python-ver }} +# - name: '🛠️ Python setup' +# uses: actions/setup-python@v5 +# with: +# python-version: ${{ matrix.config.python-ver }} - - name: '🚧 Linux x64/x86 build' - if: contains(matrix.config.arch, 'x64') || contains(matrix.config.arch, 'x86') - shell: 'script -q -e -c "bash {0}"' - run: | - if [ ${{ matrix.config.arch }} == 'x64' ]; then - sudo apt install -q -y libcmocka-dev ninja-build - else - export CFLAGS="-m32" LDFLAGS="-m32" LDFLAGS_STATIC="-m32" UNICORN_QEMU_FLAGS="--cpu=i386" - sudo dpkg --add-architecture i386 - sudo apt install -q -y lib32ncurses-dev lib32z1-dev lib32gcc-9-dev libc6-dev-i386 gcc-multilib \ - libcmocka-dev:i386 libcmocka0:i386 libc6:i386 libgcc-s1:i386 ninja-build - fi - mkdir build - mkdir instdir - cmake \ - -S . \ - -B . \ - -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ - -G "${{ matrix.config.generators }}" \ - -DCMAKE_INSTALL_PREFIX:PATH=instdir \ - -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} - cmake --build . --config ${{ matrix.config.build_type }} - cmake --install . --strip - ctest -VV -C ${{ matrix.config.build_type }} - - - name: '🚧 Linux ppc64le/aarch64 build' - if: contains(matrix.config.arch, 'ppc64le') || contains(matrix.config.arch, 'aarch64') - uses: uraimo/run-on-arch-action@v2 - with: - arch: ${{ matrix.config.arch }} - distro: ${{ matrix.config.distro }} - setup: | - mkdir -p "${PWD}/instdir" - dockerRunArgs: | - --volume "${PWD}/instdir:/instdir" - shell: /bin/sh - install: | - apt-get update -q -y - apt-get install -q -y git cmake build-essential automake libcmocka-dev pkg-config ${{ matrix.compiler }} ninja-build + - name: '🚧 Linux x64/x86 build' + if: contains(matrix.config.arch, 'x64') || contains(matrix.config.arch, 'x86') + shell: 'script -q -e -c "bash {0}"' run: | + if [ ${{ matrix.config.arch }} == 'x64' ]; then + sudo apt install -q -y libcmocka-dev ninja-build + else + export CFLAGS="-m32" LDFLAGS="-m32" LDFLAGS_STATIC="-m32" UNICORN_QEMU_FLAGS="--cpu=i386" + sudo dpkg --add-architecture i386 + sudo apt install -q -y lib32ncurses-dev lib32z1-dev lib32gcc-9-dev libc6-dev-i386 gcc-multilib \ + libcmocka-dev:i386 libcmocka0:i386 libc6:i386 libgcc-s1:i386 ninja-build + fi mkdir build + mkdir instdir cmake \ -S . \ -B . \ - -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ + -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ -G "${{ matrix.config.generators }}" \ - -DCMAKE_INSTALL_PREFIX:PATH=/instdir - cmake --build . --config ${{ matrix.config.build_type }} + -DCMAKE_INSTALL_PREFIX:PATH=instdir \ + -DBUILD_SHARED_LIBS=${{ matrix.config.shared }} + cmake --build . --config ${{ env.BUILD_TYPE }} cmake --install . --strip - ctest -VV -C ${{ matrix.config.build_type }} + ctest -VV -C ${{ env.BUILD_TYPE }} + + - name: '🚧 Linux ppc64le/aarch64 build' + if: contains(matrix.config.arch, 'ppc64le') || contains(matrix.config.arch, 'aarch64') + uses: uraimo/run-on-arch-action@v2 + with: + arch: ${{ matrix.config.arch }} + distro: ${{ matrix.config.distro }} + setup: | + mkdir -p "${PWD}/instdir" + dockerRunArgs: | + --volume "${PWD}/instdir:/instdir" + shell: /bin/sh + install: | + apt-get update -q -y + apt-get install -q -y git cmake build-essential automake libcmocka-dev pkg-config ${{ matrix.compiler }} ninja-build + run: | + mkdir build + cmake \ + -S . \ + -B . \ + -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ + -G "${{ matrix.config.generators }}" \ + -DCMAKE_INSTALL_PREFIX:PATH=/instdir + cmake --build . --config ${{ env.BUILD_TYPE }} + cmake --install . --strip + ctest -VV -C ${{ env.BUILD_TYPE }} - - name: '📦 Pack artifact' - if: always() - shell: bash - working-directory: instdir - run: | - ls -laR - ${{ matrix.config.archiver }} ../${{ matrix.config.artifact }} . ../test* + - name: '📦 Pack artifact' + if: always() + shell: bash + working-directory: instdir + run: | + ls -laR + ${{ matrix.config.archiver }} ../${{ matrix.config.artifact }} . ../test* - - name: '📤 Upload artifact' - if: always() - uses: actions/upload-artifact@v4 - with: - path: ./${{ matrix.config.artifact }} - name: ${{ matrix.config.artifact }} \ No newline at end of file + - name: '📤 Upload artifact' + if: always() + uses: actions/upload-artifact@v4 + with: + path: ./${{ matrix.config.artifact }} + name: ${{ matrix.config.artifact }} diff --git a/.github/workflows/build-wheels-publish.yml b/.github/workflows/build-wheels-publish.yml new file mode 100644 index 0000000000..39d860226d --- /dev/null +++ b/.github/workflows/build-wheels-publish.yml @@ -0,0 +1,360 @@ +name: Build wheels with cibuildwheel + +on: + push: + paths-ignore: + - ".gitignore" + - "AUTHORS.TXT" + - "COPYING" + - "COPYING.LGPL2" + - "COPYING_GLIB" + - "CREDITS.TXT" + - "ChangeLog" + - "README.md" + - "docs/**" + pull_request: + +env: + # Enable DEBUG flag if not tag release + UNICORN_DEBUG: ${{ startsWith(github.ref, 'refs/tags') && '0' || '1' }} + +jobs: + # job to be executed for every push - testing purpose + build_wheels_python38_only: + name: Building on ${{ matrix.os }} - ${{ matrix.arch }} ${{ matrix.cibw_build }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + # NOTE: aarch64 builds are super slow due to QEMU emulation. Making this to parallelize and speed up workflow + # i686 - manylinux + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp38-manylinux' } + # i686 - musllinux + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp38-musllinux' } + # x86_64 - manylinux + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp38-manylinux' } + # x86_64 - musllinux + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp38-musllinux' } + # aarch64 - manylinux + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp38-manylinux' } + # aarch64 - musllinux + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp38-musllinux' } + - { os: macos-13, arch: x86_64, cibw_build: '' } + - { os: macos-latest, arch: arm64, cibw_build: '' } + - { os: windows-2019, arch: AMD64, cibw_build: '' } + - { os: windows-2019, arch: x86, cibw_build: '' } + + steps: + - uses: actions/checkout@v4 + + - name: '🛠️ Add msbuild to PATH' + if: runner.os == 'Windows' + uses: microsoft/setup-msbuild@v2 + with: + vs-version: '16.5' + + - name: '🛠️ Win build dependencies' + if: runner.os == 'Windows' + shell: bash + run: | + choco install ninja cmake + + - name: '🛠️ macOS dependencies' + if: runner.os == 'macOS' + run: | + brew install ninja + + # https://cibuildwheel.pypa.io/en/stable/faq/#macos-building-cpython-38-wheels-on-arm64 + - uses: actions/setup-python@v5 + if: runner.os == 'macOS' && runner.arch == 'ARM64' + with: + python-version: 3.8 + + - name: '🛠️ Win MSVC 32 dev cmd setup' + if: runner.os == 'Windows' && matrix.arch == 'x86' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x86 + + - name: '🛠️ Win MSVC 64 dev cmd setup' + if: runner.os == 'Windows' && matrix.arch == 'AMD64' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: '🛠️ Set up QEMU' + if: runner.os == 'Linux' + uses: docker/setup-qemu-action@v3 + + - name: '🚧 cibuildwheel run - Linux' + if: matrix.os == 'ubuntu-latest' + uses: pypa/cibuildwheel@v2.21.3 + env: + CIBW_BUILD_FRONTEND: build + CIBW_BUILD: ${{ matrix.cibw_build }}* + CIBW_ARCHS: ${{ matrix.arch }} + CIBW_ENVIRONMENT: DEBUG=${{ env.UNICORN_DEBUG }} + CIBW_ENVIRONMENT_PASS_LINUX: DEBUG + CIBW_TEST_REQUIRES: pytest + CIBW_TEST_COMMAND: pytest {package}/tests + with: + package-dir: bindings/python + output-dir: wheelhouse + + - name: '🚧 cibuildwheel run - Windows' + if: matrix.os == 'windows-2019' + uses: pypa/cibuildwheel@v2.21.3 + env: + CIBW_BUILD_FRONTEND: build + CIBW_BUILD: 'cp38*' + CIBW_ARCHS: ${{ matrix.arch }} + CIBW_ENVIRONMENT: DEBUG=${{ env.UNICORN_DEBUG }} + CIBW_TEST_REQUIRES: pytest + CIBW_TEST_COMMAND: pytest {package}/tests + with: + package-dir: bindings/python + output-dir: wheelhouse + + - name: '🚧 cibuildwheel run - MacOS x86_84' + if: matrix.os == 'macos-13' + uses: pypa/cibuildwheel@v2.21.3 + env: + CIBW_BUILD_FRONTEND: build + CIBW_BUILD: 'cp38*' + CIBW_ENVIRONMENT: SYSTEM_VERSION_COMPAT=0 DEBUG=${{ env.UNICORN_DEBUG }} + CIBW_TEST_REQUIRES: pytest + CIBW_TEST_COMMAND: pytest {package}/tests + with: + package-dir: bindings/python + output-dir: wheelhouse + + - name: '🚧 cibuildwheel run - MacOS arm64' + if: matrix.os == 'macos-latest' + uses: pypa/cibuildwheel@v2.21.3 + env: + CIBW_BUILD_FRONTEND: build + CIBW_BUILD: 'cp38*' + CIBW_ENVIRONMENT: DEBUG=${{ env.UNICORN_DEBUG }} + CIBW_TEST_REQUIRES: pytest + CIBW_TEST_COMMAND: pytest {package}/tests + # https://github.com/pypa/cibuildwheel/pull/1169 + CIBW_TEST_SKIP: "cp38-macosx_*:arm64" + with: + package-dir: bindings/python + output-dir: wheelhouse + + # we re-tag cp38 wheel (just an old one) with py2 tag. Hacky but it works... + - name: '🚧 Python 2.7 wheels re-tagging - Windows' + if: matrix.os == 'windows-2019' + run: | + python -m pip install -U pip wheel && Get-ChildItem -Path wheelhouse/ -Filter *cp38*.whl | Foreach-Object { + python -m wheel tags --python-tag='py2' --abi-tag=none $_.FullName + } + - name: '🚧 Python 2.7 wheels re-tagging - Non-Windows' + if: matrix.os != 'windows-2019' + env: + PIP_BREAK_SYSTEM_PACKAGES: 1 + run: | + python3 -m pip install -U pip wheel && python3 -m wheel tags --python-tag='py2' --abi-tag=none wheelhouse/*cp38*.whl + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + # Job to be executed to build all wheels for all platforms/architectures/python versions only for tag release + build_wheels_all_versions: + name: Building on ${{ matrix.os }} - ${{ matrix.arch }} ${{ matrix.cibw_build }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + # NOTE: aarch64 builds are super slow due to QEMU emulation. Making this to parallelize and speed up workflow + # i686 - manylinux + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp37-manylinux' } + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp39-manylinux' } + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp310-manylinux' } + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp311-manylinux' } + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp312-manylinux' } + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp313-manylinux' } + # i686 - musllinux + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp37-musllinux' } + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp39-musllinux' } + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp310-musllinux' } + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp311-musllinux' } + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp312-musllinux' } + - { os: ubuntu-latest, arch: i686, cibw_build: 'cp313-musllinux' } + # x86_64 - manylinux + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp37-manylinux' } + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp39-manylinux' } + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp310-manylinux' } + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp311-manylinux' } + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp312-manylinux' } + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp313-manylinux' } + # x86_64 - musllinux + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp37-musllinux' } + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp39-musllinux' } + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp310-musllinux' } + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp311-musllinux' } + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp312-musllinux' } + - { os: ubuntu-latest, arch: x86_64, cibw_build: 'cp313-musllinux' } + # aarch64 - manylinux + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp37-manylinux' } + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp39-manylinux' } + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp310-manylinux' } + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp311-manylinux' } + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp312-manylinux' } + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp313-manylinux' } + # aarch64 - musllinux + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp37-musllinux' } + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp39-musllinux' } + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp310-musllinux' } + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp311-musllinux' } + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp312-musllinux' } + - { os: ubuntu-latest, arch: aarch64, cibw_build: 'cp313-musllinux' } + - { os: macos-13, arch: x86_64, cibw_build: '' } + - { os: macos-latest, arch: arm64, cibw_build: '' } + - { os: windows-2019, arch: AMD64, cibw_build: '' } + - { os: windows-2019, arch: x86, cibw_build: '' } + if: startsWith(github.ref, 'refs/tags') + steps: + - uses: actions/checkout@v4 + + - name: '🛠️ Add msbuild to PATH' + if: runner.os == 'Windows' + uses: microsoft/setup-msbuild@v2 + with: + vs-version: '16.5' + + - name: '🛠️ Win build dependencies' + if: runner.os == 'Windows' + shell: bash + run: | + choco install ninja cmake + + - name: '🛠️ macOS dependencies' + if: runner.os == 'macOS' + run: | + brew install ninja + + - name: '🛠️ Win MSVC 32 dev cmd setup' + if: runner.os == 'Windows' && matrix.arch == 'x86' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x86 + + - name: '🛠️ Win MSVC 64 dev cmd setup' + if: runner.os == 'Windows' && matrix.arch == 'AMD64' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: '🛠️ Set up QEMU' + if: runner.os == 'Linux' + uses: docker/setup-qemu-action@v3 + + - name: '🚧 cibuildwheel run - Linux' + if: matrix.os == 'ubuntu-latest' + uses: pypa/cibuildwheel@v2.21.3 + env: + CIBW_BUILD_FRONTEND: build + CIBW_BUILD: ${{ matrix.cibw_build }}* + CIBW_ARCHS: ${{ matrix.arch }} + CIBW_ENVIRONMENT: DEBUG=${{ env.UNICORN_DEBUG }} + CIBW_ENVIRONMENT_PASS_LINUX: DEBUG + CIBW_TEST_REQUIRES: pytest + CIBW_TEST_COMMAND: pytest {package}/tests + with: + package-dir: bindings/python + output-dir: wheelhouse + + - name: '🚧 cibuildwheel run - Windows' + if: matrix.os == 'windows-2019' + uses: pypa/cibuildwheel@v2.21.3 + env: + CIBW_BUILD_FRONTEND: build + CIBW_SKIP: '*36* *38*' + CIBW_BUILD: 'cp*' + CIBW_ENVIRONMENT: DEBUG=${{ env.UNICORN_DEBUG }} + CIBW_ARCHS: ${{ matrix.arch }} + CIBW_TEST_REQUIRES: pytest + CIBW_TEST_COMMAND: pytest {package}/tests + with: + package-dir: bindings/python + output-dir: wheelhouse + + - name: '🚧 cibuildwheel run - MacOS x86_84' + if: matrix.os == 'macos-13' + uses: pypa/cibuildwheel@v2.21.3 + env: + CIBW_BUILD_FRONTEND: build + CIBW_SKIP: '*36* *38*' + CIBW_BUILD: 'cp*' + CIBW_ENVIRONMENT: DEBUG=${{ env.UNICORN_DEBUG }} + CIBW_TEST_REQUIRES: pytest + CIBW_TEST_COMMAND: pytest {package}/tests + with: + package-dir: bindings/python + output-dir: wheelhouse + + - name: '🚧 cibuildwheel run - MacOS arm64' + if: matrix.os == 'macos-latest' + uses: pypa/cibuildwheel@v2.21.3 + env: + CIBW_BUILD_FRONTEND: build + CIBW_SKIP: '*36* *37* *38*' + CIBW_BUILD: 'cp*' + CIBW_ENVIRONMENT: DEBUG=${{ env.UNICORN_DEBUG }} + CIBW_TEST_REQUIRES: pytest + CIBW_TEST_COMMAND: pytest {package}/tests + with: + package-dir: bindings/python + output-dir: wheelhouse + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + make_sdist: + name: Make SDist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Optional, use if you use setuptools_scm + submodules: true # Optional, use if you have submodules + + - name: Build SDist + run: | + cd bindings/python + python3 -m pip install -U pip build + python3 -m build --sdist + + - uses: actions/upload-artifact@v4 + with: + path: bindings/python/dist/*.tar.gz + + publish: + needs: [ build_wheels_python38_only, build_wheels_all_versions, make_sdist ] + environment: pypi + permissions: + id-token: write + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags') + steps: + - uses: actions/download-artifact@v4 + with: + merge-multiple: true + path: dist + + - name: Show downloaded artifacts + run: ls -laR dist + + - name: '📦 Publish distribution to PyPI' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.pypi_pass }} diff --git a/.gitignore b/.gitignore index 98fa46f04e..d62eca0468 100644 --- a/.gitignore +++ b/.gitignore @@ -90,4 +90,10 @@ packages/ cmocka/ zig-cache/ zig-out/ -.cache \ No newline at end of file +.cache + +################## +## PyCharm Project +################## + +.idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fcde11770..2c389bc34f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,24 @@ if(APPLE AND NOT CMAKE_C_COMPILER) set(CMAKE_C_COMPILER "/usr/bin/cc") endif() +# Source: https://github.com/capstone-engine/capstone/blob/next/CMakeLists.txt +# If building for OSX it's best to allow CMake to handle building both architectures +if(APPLE) + # The cibuildwheel on Github Actions sets this env variable + # with the architecture flags it wants to build for. + if(DEFINED ENV{ARCHFLAGS}) + if("$ENV{ARCHFLAGS}" STREQUAL "-arch arm64 -arch x86_64") + set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") + elseif("$ENV{ARCHFLAGS}" STREQUAL "-arch arm64") + set(CMAKE_OSX_ARCHITECTURES "arm64") + elseif("$ENV{ARCHFLAGS}" STREQUAL "-arch x86_64") + set(CMAKE_OSX_ARCHITECTURES "x86_64") + endif() + else() + set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") + endif() +endif() + # Detect if unicorn is compiled as the top-level project set(PROJECT_IS_TOP_LEVEL OFF) if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) diff --git a/bindings/python/MANIFEST.in b/bindings/python/MANIFEST.in index a98ea526a4..8d1b49e2b9 100644 --- a/bindings/python/MANIFEST.in +++ b/bindings/python/MANIFEST.in @@ -1,4 +1,9 @@ recursive-include src * recursive-include prebuilt * -include LICENSE.TXT -include README.TXT +graft unicorn/lib +graft unicorn/include +global-include *.a +global-include *.so.2 +global-include *.*lib +global-include *.dll +global-include *.h diff --git a/bindings/python/README.md b/bindings/python/README.md index d44f6dd066..21c84432d7 100644 --- a/bindings/python/README.md +++ b/bindings/python/README.md @@ -1,3 +1,21 @@ +# Unicorn + +Unicorn is a lightweight, multi-platform, multi-architecture CPU emulator framework +based on [QEMU](http://qemu.org). + +Unicorn offers some unparalleled features: + +- Multi-architecture: ARM, ARM64 (ARMv8), M68K, MIPS, PowerPC, RISCV, SPARC, S390X, TriCore and X86 (16, 32, 64-bit) +- Clean/simple/lightweight/intuitive architecture-neutral API +- Implemented in pure C language, with bindings for Crystal, Clojure, Visual Basic, Perl, Rust, Ruby, Python, Java, .NET, Go, Delphi/Free Pascal, Haskell, Pharo, and Lua. +- Native support for Windows & *nix (with Mac OSX, Linux, *BSD & Solaris confirmed) +- High performance via Just-In-Time compilation +- Support for fine-grained instrumentation at various levels +- Thread-safety by design +- Distributed under free software license GPLv2 + +Further information is available at http://www.unicorn-engine.org + # Python Bindings for Unicorn Originally written by Nguyen Anh Quynh, polished and redesigned by elicn, maintained by all community contributors. diff --git a/bindings/python/build_wheel.sh b/bindings/python/build_wheel.sh deleted file mode 100755 index 70ff32018d..0000000000 --- a/bindings/python/build_wheel.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -e -x - -cd bindings/python - -# Compile wheels -python3.7 setup.py bdist_wheel $@ -cd dist - -# We can't repair an aarch64 wheel on x64 hosts -# https://github.com/pypa/auditwheel/issues/244 -if [[ ! "$*" =~ "aarch64" ]];then - auditwheel repair *.whl - mv -f wheelhouse/*.whl . -fi diff --git a/bindings/python/musl_wheel.sh b/bindings/python/musl_wheel.sh deleted file mode 100644 index 5bc5ff3a94..0000000000 --- a/bindings/python/musl_wheel.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -# TODO: use cibuildwheel -apk update -apk add gcc make cmake pkgconfig linux-headers git musl-dev patchelf - -python3 -m pip install -U pip setuptools auditwheel - -cd bindings/python && python3 setup.py bdist_wheel && auditwheel repair dist/*.whl && mv -f wheelhouse/*.whl ./dist/ \ No newline at end of file diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml new file mode 100644 index 0000000000..6857ca1cbf --- /dev/null +++ b/bindings/python/pyproject.toml @@ -0,0 +1,40 @@ +[build-system] +requires = ["setuptools", "build", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "unicorn" +version = "2.1.1" +requires-python = ">= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*, != 3.4.*, != 3.5.*, != 3.6.*" +authors = [ + { name = "Nguyen Anh Quynh", email = "quynh@gmail.com" }, +] +description = "Unicorn CPU emulator engine" +readme = "README.md" +keywords = ["emulation", "qemu", "unicorn"] +classifiers = [ + 'License :: OSI Approved :: BSD License', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', +] + +[project.urls] +Homepage = "http://www.unicorn-engine.org" +Repository = "https://github.com/unicorn-engine/unicorn" +"Bug Tracker" = "https://github.com/unicorn-engine/unicorn/issues" +Changelog = "https://github.com/unicorn-engine/unicorn/blob/master/ChangeLog" + +[project.optional-dependencies] +test = [ + "pytest", + "pytest-cov", +] + +[tool.setuptools.packages.find] +include = ["unicorn*"] diff --git a/bindings/python/sample_all.sh b/bindings/python/sample_all.sh deleted file mode 100755 index 378c139da4..0000000000 --- a/bindings/python/sample_all.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh - -python3 ./sample_arm.py -echo "==========================" -python3 ./sample_armeb.py -echo "==========================" -python3 ./sample_arm64.py -echo "==========================" -python3 ./sample_arm64eb.py -echo "==========================" -python3 ./sample_m68k.py -echo "==========================" -python3 ./sample_mips.py -echo "==========================" -python3 ./sample_ppc.py -echo "==========================" -python3 ./sample_riscv.py -echo "==========================" -python3 ./sample_s390x.py -echo "==========================" -python3 ./sample_sparc.py -echo "==========================" -python3 ./sample_tricore.py -echo "==========================" -python3 ./sample_x86.py -echo "==========================" -python3 ./shellcode.py -echo "==========================" -python3 ./sample_ctl.py -echo "==========================" -python3 ./sample_network_auditing.py diff --git a/bindings/python/setup.cfg b/bindings/python/setup.cfg deleted file mode 100644 index 3c6e79cf31..0000000000 --- a/bindings/python/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal=1 diff --git a/bindings/python/setup.py b/bindings/python/setup.py index 85f9036481..270f215232 100755 --- a/bindings/python/setup.py +++ b/bindings/python/setup.py @@ -1,29 +1,19 @@ -#!/usr/bin/env python # Python binding for Unicorn engine. Nguyen Anh Quynh -from __future__ import print_function import glob import logging import os -import subprocess +import platform import shutil +import subprocess import sys -import platform -import setuptools - from setuptools import setup -from sysconfig import get_platform from setuptools.command.build import build from setuptools.command.sdist import sdist from setuptools.command.bdist_egg import bdist_egg log = logging.getLogger(__name__) -SYSTEM = sys.platform - -# sys.maxint is 2**31 - 1 on both 32 and 64 bit mingw -IS_64BITS = platform.architecture()[0] == '64bit' - # are we building from the repository or from a source distribution? ROOT_DIR = os.path.dirname(os.path.realpath(__file__)) LIBS_DIR = os.path.join(ROOT_DIR, 'unicorn', 'lib') @@ -32,29 +22,28 @@ UC_DIR = SRC_DIR if os.path.exists(SRC_DIR) else os.path.join(ROOT_DIR, '../..') BUILD_DIR = os.path.join(UC_DIR, 'build_python') -VERSION = "2.1.1" - -if SYSTEM == 'darwin': +if sys.platform == 'darwin': LIBRARY_FILE = "libunicorn.2.dylib" STATIC_LIBRARY_FILE = "libunicorn.a" -elif SYSTEM in ('win32', 'cygwin'): +elif sys.platform in ('win32', 'cygwin'): LIBRARY_FILE = "unicorn.dll" STATIC_LIBRARY_FILE = "unicorn.lib" else: LIBRARY_FILE = "libunicorn.so.2" STATIC_LIBRARY_FILE = "libunicorn.a" + def clean_bins(): shutil.rmtree(LIBS_DIR, ignore_errors=True) shutil.rmtree(HEADERS_DIR, ignore_errors=True) + def copy_sources(): - """Copy the C sources into the source directory. + """ + Copy the C sources into the source directory. This rearranges the source files under the python distribution directory. """ - src = [] - shutil.rmtree(SRC_DIR, ignore_errors=True) os.mkdir(SRC_DIR) @@ -66,17 +55,16 @@ def copy_sources(): shutil.copytree(os.path.join(ROOT_DIR, '../../samples'), os.path.join(SRC_DIR, 'samples/')) shutil.copytree(os.path.join(ROOT_DIR, '../../glib_compat'), os.path.join(SRC_DIR, 'glib_compat/')) shutil.copytree(os.path.join(ROOT_DIR, '../../cmake'), os.path.join(SRC_DIR, 'cmake/')) - + try: - # remove site-specific configuration file - # might not exist + # remove site-specific configuration file, might not exist os.remove(os.path.join(SRC_DIR, 'qemu/config-host.mak')) except OSError: pass + src = [] src.extend(glob.glob(os.path.join(ROOT_DIR, "../../*.[ch]"))) src.extend(glob.glob(os.path.join(ROOT_DIR, "../../*.mk"))) - src.extend(glob.glob(os.path.join(ROOT_DIR, "../../LICENSE*"))) src.extend(glob.glob(os.path.join(ROOT_DIR, "../../README.md"))) src.extend(glob.glob(os.path.join(ROOT_DIR, "../../*.TXT"))) @@ -87,6 +75,7 @@ def copy_sources(): log.info("%s -> %s" % (filename, outpath)) shutil.copy(filename, outpath) + def build_libraries(): """ Prepare the unicorn directory for a binary distribution or installation. @@ -94,7 +83,6 @@ def build_libraries(): Will use a src/ dir if one exists in the current directory, otherwise assumes it's in the repo """ - cwd = os.getcwd() clean_bins() os.mkdir(HEADERS_DIR) os.mkdir(LIBS_DIR) @@ -102,158 +90,84 @@ def build_libraries(): # copy public headers shutil.copytree(os.path.join(UC_DIR, 'include', 'unicorn'), os.path.join(HEADERS_DIR, 'unicorn')) - # check if a prebuilt library exists - # if so, use it instead of building + # check if a prebuilt library exists and if so, use it instead of building if os.path.exists(os.path.join(ROOT_DIR, 'prebuilt', LIBRARY_FILE)): shutil.copy(os.path.join(ROOT_DIR, 'prebuilt', LIBRARY_FILE), LIBS_DIR) if STATIC_LIBRARY_FILE is not None and os.path.exists(os.path.join(ROOT_DIR, 'prebuilt', STATIC_LIBRARY_FILE)): shutil.copy(os.path.join(ROOT_DIR, 'prebuilt', STATIC_LIBRARY_FILE), LIBS_DIR) return - # otherwise, build!! - os.chdir(UC_DIR) + # otherwise, build + if not os.path.exists(BUILD_DIR): + os.mkdir(BUILD_DIR) - try: - subprocess.check_call(['msbuild', '/help']) - except: - has_msbuild = False - else: - has_msbuild = True + has_msbuild = shutil.which('msbuild') is not None + conf = 'Debug' if int(os.getenv('DEBUG', 0)) else 'Release' - if has_msbuild and SYSTEM == 'win32': + if has_msbuild and sys.platform == 'win32': plat = 'Win32' if platform.architecture()[0] == '32bit' else 'x64' - conf = 'Debug' if os.getenv('DEBUG', '') else 'Release' - if not os.path.exists(BUILD_DIR): - os.mkdir(BUILD_DIR) - - subprocess.check_call(['cmake', '-B', BUILD_DIR, '-G', "Visual Studio 16 2019", "-A", plat, "-DCMAKE_BUILD_TYPE=" + conf]) - subprocess.check_call(['msbuild', 'unicorn.sln', '-m', '-p:Platform=' + plat, '-p:Configuration=' + conf], cwd=BUILD_DIR) + + subprocess.check_call(['cmake', '-B', BUILD_DIR, '-G', "Visual Studio 16 2019", "-A", plat, + "-DCMAKE_BUILD_TYPE=" + conf], cwd=UC_DIR) + subprocess.check_call(['msbuild', 'unicorn.sln', '-m', '-p:Platform=' + plat, '-p:Configuration=' + conf], + cwd=BUILD_DIR) obj_dir = os.path.join(BUILD_DIR, conf) shutil.copy(os.path.join(obj_dir, LIBRARY_FILE), LIBS_DIR) shutil.copy(os.path.join(BUILD_DIR, STATIC_LIBRARY_FILE), LIBS_DIR) else: - # platform description refs at https://docs.python.org/2/library/sys.html#sys.platform - if not os.path.exists(BUILD_DIR): - os.mkdir(BUILD_DIR) - conf = 'Debug' if os.getenv('DEBUG', '') else 'Release' - cmake_args = ["cmake", '-B', BUILD_DIR, '-S', UC_DIR, "-DCMAKE_BUILD_TYPE=" + conf] - if os.getenv("TRACE", ""): + if os.getenv("TRACE"): cmake_args += ["-DUNICORN_TRACER=on"] - subprocess.check_call(cmake_args) - os.chdir(BUILD_DIR) + subprocess.check_call(cmake_args, cwd=UC_DIR) threads = os.getenv("THREADS", "4") - subprocess.check_call(["cmake", "--build", ".", "-j" + threads]) - - shutil.copy(LIBRARY_FILE, LIBS_DIR) - shutil.copy(STATIC_LIBRARY_FILE, LIBS_DIR) + subprocess.check_call(["cmake", "--build", ".", "-j" + threads], cwd=BUILD_DIR) - os.chdir(cwd) + shutil.copy(os.path.join(BUILD_DIR, LIBRARY_FILE), LIBS_DIR) + shutil.copy(os.path.join(BUILD_DIR, STATIC_LIBRARY_FILE), LIBS_DIR) -class custom_sdist(sdist): +class CustomSDist(sdist): def run(self): clean_bins() copy_sources() - return sdist.run(self) + return super().run() + -class custom_build(build): +class CustomBuild(build): def run(self): if 'LIBUNICORN_PATH' in os.environ: log.info("Skipping building C extensions since LIBUNICORN_PATH is set") else: log.info("Building C extensions") build_libraries() - return build.run(self) + return super().run() -class custom_bdist_egg(bdist_egg): + +class CustomBDistEgg(bdist_egg): def run(self): self.run_command('build') - return bdist_egg.run(self) - -def dummy_src(): - return [] - -cmdclass = {} -cmdclass['build'] = custom_build -cmdclass['sdist'] = custom_sdist -cmdclass['bdist_egg'] = custom_bdist_egg - -if 'bdist_wheel' in sys.argv and '--plat-name' not in sys.argv: - idx = sys.argv.index('bdist_wheel') + 1 - sys.argv.insert(idx, '--plat-name') - name = get_platform() - if 'linux' in name: - # linux_* platform tags are disallowed because the python ecosystem is fubar - # linux builds should be built in the centos 5 vm for maximum compatibility - # see https://github.com/pypa/manylinux - # see also https://github.com/angr/angr-dev/blob/master/bdist.sh - sys.argv.insert(idx + 1, 'manylinux1_' + platform.machine()) - elif 'mingw' in name: - if IS_64BITS: - sys.argv.insert(idx + 1, 'win_amd64') - else: - sys.argv.insert(idx + 1, 'win32') - else: - # https://www.python.org/dev/peps/pep-0425/ - sys.argv.insert(idx + 1, name.replace('.', '_').replace('-', '_')) + return super().run() + + +cmdclass = {'build': CustomBuild, 'sdist': CustomSDist, 'bdist_egg': CustomBDistEgg} try: from setuptools.command.develop import develop - class custom_develop(develop): + + + class CustomDevelop(develop): def run(self): log.info("Building C extensions") build_libraries() - return develop.run(self) + return super().run() - cmdclass['develop'] = custom_develop + + cmdclass['develop'] = CustomDevelop except ImportError: print("Proper 'develop' support unavailable.") -def join_all(src, files): - return tuple(os.path.join(src, f) for f in files) - -long_desc = ''' -Unicorn is a lightweight, multi-platform, multi-architecture CPU emulator framework -based on [QEMU](http://qemu.org). - -Unicorn offers some unparalleled features: - -- Multi-architecture: ARM, ARM64 (ARMv8), M68K, MIPS, PowerPC, RISCV, SPARC, S390X, TriCore and X86 (16, 32, 64-bit) -- Clean/simple/lightweight/intuitive architecture-neutral API -- Implemented in pure C language, with bindings for Crystal, Clojure, Visual Basic, Perl, Rust, Ruby, Python, Java, .NET, Go, Delphi/Free Pascal, Haskell, Pharo, and Lua. -- Native support for Windows & *nix (with Mac OSX, Linux, *BSD & Solaris confirmed) -- High performance via Just-In-Time compilation -- Support for fine-grained instrumentation at various levels -- Thread-safety by design -- Distributed under free software license GPLv2 - -Further information is available at http://www.unicorn-engine.org -''' - setup( - provides=['unicorn'], - packages=setuptools.find_packages(include=["unicorn", "unicorn.*"]), - name='unicorn', - version=VERSION, - author='Nguyen Anh Quynh', - author_email='aquynh@gmail.com', - description='Unicorn CPU emulator engine', - long_description=long_desc, - long_description_content_type="text/markdown", - url='http://www.unicorn-engine.org', - classifiers=[ - 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 3', - ], - requires=['ctypes'], cmdclass=cmdclass, - zip_safe=False, - include_package_data=True, - is_pure=False, - package_data={ - 'unicorn': ['unicorn/py.typed', 'lib/*', 'include/unicorn/*'] - } + has_ext_modules=lambda: True, # It's not a Pure Python wheel ) diff --git a/bindings/python/sample_arm.py b/bindings/python/tests/test_arm.py similarity index 89% rename from bindings/python/sample_arm.py rename to bindings/python/tests/test_arm.py index b906d53875..a0f7ff5824 100755 --- a/bindings/python/sample_arm.py +++ b/bindings/python/tests/test_arm.py @@ -6,22 +6,21 @@ from unicorn import * from unicorn.arm_const import * - # code to be emulated -ARM_CODE = b"\x37\x00\xa0\xe3\x03\x10\x42\xe0" # mov r0, #0x37; sub r1, r2, r3 -THUMB_CODE = b"\x83\xb0" # sub sp, #0xc +ARM_CODE = b"\x37\x00\xa0\xe3\x03\x10\x42\xe0" # mov r0, #0x37; sub r1, r2, r3 +THUMB_CODE = b"\x83\xb0" # sub sp, #0xc # memory address where emulation starts -ADDRESS = 0x10000 +ADDRESS = 0x10000 # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) # Test ARM @@ -41,8 +40,8 @@ def test_arm(): mu.reg_write(UC_ARM_REG_R0, 0x1234) mu.reg_write(UC_ARM_REG_R2, 0x6789) mu.reg_write(UC_ARM_REG_R3, 0x3333) - mu.reg_write(UC_ARM_REG_APSR, 0xFFFFFFFF) #All application flags turned on - + mu.reg_write(UC_ARM_REG_APSR, 0xFFFFFFFF) # All application flags turned on + # tracing all basic blocks with customized callback mu.hook_add(UC_HOOK_BLOCK, hook_block) @@ -57,8 +56,8 @@ def test_arm(): r0 = mu.reg_read(UC_ARM_REG_R0) r1 = mu.reg_read(UC_ARM_REG_R1) - print(">>> R0 = 0x%x" %r0) - print(">>> R1 = 0x%x" %r1) + print(">>> R0 = 0x%x" % r0) + print(">>> R1 = 0x%x" % r1) except UcError as e: print("ERROR: %s" % e) @@ -93,11 +92,12 @@ def test_thumb(): print(">>> Emulation done. Below is the CPU context") sp = mu.reg_read(UC_ARM_REG_SP) - print(">>> SP = 0x%x" %sp) + print(">>> SP = 0x%x" % sp) except UcError as e: print("ERROR: %s" % e) + def test_read_sctlr(): print("Read SCTLR") try: @@ -118,6 +118,7 @@ def test_read_sctlr(): except UcError as e: print("ERROR: %s" % e) + if __name__ == '__main__': test_arm() print("=" * 26) diff --git a/bindings/python/sample_arm64.py b/bindings/python/tests/test_arm64.py similarity index 92% rename from bindings/python/sample_arm64.py rename to bindings/python/tests/test_arm64.py index 0ebddf9139..d615f894a9 100755 --- a/bindings/python/sample_arm64.py +++ b/bindings/python/tests/test_arm64.py @@ -6,25 +6,24 @@ from unicorn import * from unicorn.arm64_const import * - # code to be emulated -ARM64_CODE = b"\xab\x05\x00\xb8\xaf\x05\x40\x38" # str x11, [x13]; ldrb x15, [x13] +ARM64_CODE = b"\xab\x05\x00\xb8\xaf\x05\x40\x38" # str x11, [x13]; ldrb x15, [x13] # MSR code -ARM64_MRS_CODE = b"\x62\xd0\x3b\xd5" # mrs x2, tpidrro_el0 +ARM64_MRS_CODE = b"\x62\xd0\x3b\xd5" # mrs x2, tpidrro_el0 # memory address where emulation starts -ADDRESS = 0x10000 +ADDRESS = 0x10000 # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) # Test ARM64 @@ -61,7 +60,7 @@ def test_arm64(): x11 = mu.reg_read(UC_ARM64_REG_X11) x13 = mu.reg_read(UC_ARM64_REG_X13) x15 = mu.reg_read(UC_ARM64_REG_X15) - print(">>> X15 = 0x%x" %x15) + print(">>> X15 = 0x%x" % x15) except UcError as e: print("ERROR: %s" % e) @@ -85,6 +84,7 @@ def test_arm64_read_sctlr(): except UcError as e: print("ERROR: %s" % e) + def test_arm64_hook_mrs(): def _hook_mrs(uc, reg, cp_reg, _): print(f">>> Hook MRS instruction: reg = 0x{reg:x}(UC_ARM64_REG_X2) cp_reg = {cp_reg}") @@ -116,6 +116,7 @@ def _hook_mrs(uc, reg, cp_reg, _): except UcError as e: print("ERROR: %s" % e) + if __name__ == '__main__': test_arm64() print("=" * 26) diff --git a/bindings/python/sample_arm64eb.py b/bindings/python/tests/test_arm64eb.py similarity index 91% rename from bindings/python/sample_arm64eb.py rename to bindings/python/tests/test_arm64eb.py index 4d96f46de7..2a8b87ae7c 100755 --- a/bindings/python/sample_arm64eb.py +++ b/bindings/python/tests/test_arm64eb.py @@ -7,22 +7,21 @@ from unicorn import * from unicorn.arm64_const import * - # code to be emulated -ARM64_CODE = b"\xab\x05\x00\xb8\xaf\x05\x40\x38" # str x11, [x13]; ldrb x15, [x13] +ARM64_CODE = b"\xab\x05\x00\xb8\xaf\x05\x40\x38" # str x11, [x13]; ldrb x15, [x13] # memory address where emulation starts -ADDRESS = 0x10000 +ADDRESS = 0x10000 # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) # Test ARM64 @@ -59,7 +58,7 @@ def test_arm64(): x11 = mu.reg_read(UC_ARM64_REG_X11) x13 = mu.reg_read(UC_ARM64_REG_X13) x15 = mu.reg_read(UC_ARM64_REG_X15) - print(">>> X15 = 0x%x" %x15) + print(">>> X15 = 0x%x" % x15) except UcError as e: print("ERROR: %s" % e) diff --git a/bindings/python/sample_armeb.py b/bindings/python/tests/test_armeb.py similarity index 88% rename from bindings/python/sample_armeb.py rename to bindings/python/tests/test_armeb.py index 9a2158dba6..f818d2746d 100755 --- a/bindings/python/sample_armeb.py +++ b/bindings/python/tests/test_armeb.py @@ -5,22 +5,21 @@ from unicorn import * from unicorn.arm_const import * - # code to be emulated -ARM_CODE = b"\xe3\xa0\x00\x37\xe0\x42\x10\x03" # mov r0, #0x37; sub r1, r2, r3 -THUMB_CODE = b"\xb0\x83" # sub sp, #0xc +ARM_CODE = b"\xe3\xa0\x00\x37\xe0\x42\x10\x03" # mov r0, #0x37; sub r1, r2, r3 +THUMB_CODE = b"\xb0\x83" # sub sp, #0xc # memory address where emulation starts -ADDRESS = 0x10000 +ADDRESS = 0x10000 # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) # Test ARM @@ -40,8 +39,8 @@ def test_arm(): mu.reg_write(UC_ARM_REG_R0, 0x1234) mu.reg_write(UC_ARM_REG_R2, 0x6789) mu.reg_write(UC_ARM_REG_R3, 0x3333) - mu.reg_write(UC_ARM_REG_APSR, 0xFFFFFFFF) #All application flags turned on - + mu.reg_write(UC_ARM_REG_APSR, 0xFFFFFFFF) # All application flags turned on + # tracing all basic blocks with customized callback mu.hook_add(UC_HOOK_BLOCK, hook_block) @@ -56,8 +55,8 @@ def test_arm(): r0 = mu.reg_read(UC_ARM_REG_R0) r1 = mu.reg_read(UC_ARM_REG_R1) - print(">>> R0 = 0x%x" %r0) - print(">>> R1 = 0x%x" %r1) + print(">>> R0 = 0x%x" % r0) + print(">>> R1 = 0x%x" % r1) except UcError as e: print("ERROR: %s" % e) @@ -92,7 +91,7 @@ def test_thumb(): print(">>> Emulation done. Below is the CPU context") sp = mu.reg_read(UC_ARM_REG_SP) - print(">>> SP = 0x%x" %sp) + print(">>> SP = 0x%x" % sp) except UcError as e: print("ERROR: %s" % e) diff --git a/bindings/python/sample_ctl.py b/bindings/python/tests/test_ctl.py similarity index 89% rename from bindings/python/sample_ctl.py rename to bindings/python/tests/test_ctl.py index c1cbc0703f..5a00340c1f 100755 --- a/bindings/python/sample_ctl.py +++ b/bindings/python/tests/test_ctl.py @@ -6,6 +6,7 @@ from unicorn.x86_const import * from datetime import datetime + def test_uc_ctl_read(): uc = Uc(UC_ARCH_X86, UC_MODE_32) @@ -21,6 +22,7 @@ def test_uc_ctl_read(): print(f">>> arch={arch} mode={mode} page size={page_size} timeout={timeout}") + def time_emulation(uc, start, end): n = datetime.now() @@ -28,6 +30,7 @@ def time_emulation(uc, start, end): return (datetime.now() - n).total_seconds() * 1e6 + def test_uc_ctl_tb_cache(): # Initialize emulator in X86-32bit mode uc = Uc(UC_ARCH_X86, UC_MODE_32) @@ -35,21 +38,21 @@ def test_uc_ctl_tb_cache(): # Fill the code buffer with NOP. code = b"\x90" * 8 * 512 - - print("Controling the TB cache in a finer granularity by uc_ctl.") + + print("Controlling the TB cache in a finer granularity by uc_ctl.") uc.mem_map(addr, 0x10000) # Write our code to the memory. uc.mem_write(addr, code) - + # Do emulation without any cache. standard = time_emulation(uc, addr, addr + len(code)) # Now we request cache for all TBs. for i in range(8): tb = uc.ctl_request_cache(addr + i * 512) - print(f">>> TB is cached at {hex(tb.pc)} which has {tb.icount} instructions with {tb.size} bytes") + print(f">>> TB is cached at {hex(tb[0])} which has {tb[1]} instructions with {tb[2]} bytes") # Do emulation with all TB cached. cached = time_emulation(uc, addr, addr + len(code)) @@ -62,12 +65,15 @@ def test_uc_ctl_tb_cache(): print(f">>> Run time: First time {standard}, Cached: {cached}, Cached evicted: {evicted}") + def trace_new_edge(uc, cur, prev, data): print(f">>> Getting a new edge from {hex(prev.pc + prev.size - 1)} to {hex(cur.pc)}") + def trace_tcg_sub(uc, address, arg1, arg2, size, data): print(f">>> Get a tcg sub opcode at {hex(address)} with args: {arg1} and {arg2}") + def test_uc_ctl_exits(): uc = Uc(UC_ARCH_X86, UC_MODE_32) addr = 0x1000 @@ -98,7 +104,7 @@ def test_uc_ctl_exits(): uc.ctl_set_exits(exits) - # This should stop at ADDRESS + 6 and increase eax, even thouhg we don't provide an exit. + # This should stop at ADDRESS + 6 and increase eax, even though we don't provide an exit. uc.emu_start(addr, 0) eax = uc.reg_read(UC_X86_REG_EAX) @@ -106,7 +112,7 @@ def test_uc_ctl_exits(): print(f">>> eax = {hex(eax)} and ebx = {hex(ebx)} after the first emulation") - # This should stop at ADDRESS + 8, even thouhg we don't provide an exit. + # This should stop at ADDRESS + 8, even though we don't provide an exit. uc.emu_start(addr, 0) eax = uc.reg_read(UC_X86_REG_EAX) @@ -114,9 +120,10 @@ def test_uc_ctl_exits(): print(f">>> eax = {hex(eax)} and ebx = {hex(ebx)} after the first emulation") + if __name__ == "__main__": test_uc_ctl_read() - print("="*32) + print("=" * 32) test_uc_ctl_tb_cache() - print("="*32) - test_uc_ctl_exits() \ No newline at end of file + print("=" * 32) + test_uc_ctl_exits() diff --git a/bindings/python/sample_m68k.py b/bindings/python/tests/test_m68k.py similarity index 96% rename from bindings/python/sample_m68k.py rename to bindings/python/tests/test_m68k.py index 7e35cf5974..57b05ea61a 100755 --- a/bindings/python/sample_m68k.py +++ b/bindings/python/tests/test_m68k.py @@ -6,21 +6,20 @@ from unicorn import * from unicorn.m68k_const import * - # code to be emulated -M68K_CODE = b"\x76\xed" # movq #-19, %d3 +M68K_CODE = b"\x76\xed" # movq #-19, %d3 # memory address where emulation starts -ADDRESS = 0x10000 +ADDRESS = 0x10000 # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) # Test ARM diff --git a/bindings/python/sample_mips.py b/bindings/python/tests/test_mips.py similarity index 91% rename from bindings/python/sample_mips.py rename to bindings/python/tests/test_mips.py index 7199b63174..3ec5ec565b 100755 --- a/bindings/python/sample_mips.py +++ b/bindings/python/tests/test_mips.py @@ -6,23 +6,22 @@ from unicorn import * from unicorn.mips_const import * - # code to be emulated -MIPS_CODE_EB = b"\x34\x21\x34\x56" # ori $at, $at, 0x3456; -MIPS_CODE_EL = b"\x56\x34\x21\x34" # ori $at, $at, 0x3456; +MIPS_CODE_EB = b"\x34\x21\x34\x56" # ori $at, $at, 0x3456; +MIPS_CODE_EL = b"\x56\x34\x21\x34" # ori $at, $at, 0x3456; # memory address where emulation starts -ADDRESS = 0x10000 +ADDRESS = 0x10000 # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) # Test MIPS EB @@ -54,7 +53,7 @@ def test_mips_eb(): print(">>> Emulation done. Below is the CPU context") r1 = mu.reg_read(UC_MIPS_REG_1) - print(">>> R1 = 0x%x" %r1) + print(">>> R1 = 0x%x" % r1) except UcError as e: print("ERROR: %s" % e) @@ -89,7 +88,7 @@ def test_mips_el(): print(">>> Emulation done. Below is the CPU context") r1 = mu.reg_read(UC_MIPS_REG_1) - print(">>> R1 = 0x%x" %r1) + print(">>> R1 = 0x%x" % r1) except UcError as e: print("ERROR: %s" % e) diff --git a/bindings/python/sample_network_auditing.py b/bindings/python/tests/test_network_auditing.py similarity index 99% rename from bindings/python/sample_network_auditing.py rename to bindings/python/tests/test_network_auditing.py index bfccf828ec..1d0c7a7875 100755 --- a/bindings/python/sample_network_auditing.py +++ b/bindings/python/tests/test_network_auditing.py @@ -3,10 +3,11 @@ # Nguyen Tan Cong from __future__ import print_function -from unicorn import * -from unicorn.x86_const import * +import pytest import struct import uuid +from unicorn import * +from unicorn.x86_const import * SIZE_REG = 4 SOCKETCALL_MAX_ARGS = 3 @@ -360,6 +361,7 @@ def hook_intr(uc, intno, user_data): print_sockcall(msg) +@pytest.mark.parametrize("code", [X86_SEND_ETCPASSWD, X86_BIND_TCP, X86_REVERSE_TCP, X86_REVERSE_TCP_2]) # Test X86 32 bit def test_i386(code): global fd_chains diff --git a/bindings/python/sample_ppc.py b/bindings/python/tests/test_ppc.py similarity index 93% rename from bindings/python/sample_ppc.py rename to bindings/python/tests/test_ppc.py index 1a0d879de0..e0cbbb0e1a 100755 --- a/bindings/python/sample_ppc.py +++ b/bindings/python/tests/test_ppc.py @@ -1,26 +1,24 @@ #!/usr/bin/env python # Sample code for PPC of Unicorn. Nguyen Anh Quynh -# from __future__ import print_function from unicorn import * from unicorn.ppc_const import * - # code to be emulated -PPC_CODE = b"\x7F\x46\x1A\x14" # add r26, r6, r3 +PPC_CODE = b"\x7F\x46\x1A\x14" # add r26, r6, r3 # memory address where emulation starts -ADDRESS = 0x10000 +ADDRESS = 0x10000 # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) # Test PPC @@ -62,4 +60,3 @@ def test_ppc(): if __name__ == '__main__': test_ppc() - diff --git a/bindings/python/sample_riscv.py b/bindings/python/tests/test_riscv.py similarity index 92% rename from bindings/python/sample_riscv.py rename to bindings/python/tests/test_riscv.py index 3bd032bdd6..39cf83f530 100755 --- a/bindings/python/sample_riscv.py +++ b/bindings/python/tests/test_riscv.py @@ -1,12 +1,10 @@ #!/usr/bin/env python # Sample code for RISCV of Unicorn. Nguyen Anh Quynh -# from __future__ import print_function from unicorn import * from unicorn.riscv_const import * - ''' $ cstool riscv64 1305100093850502 0 13 05 10 00 addi a0, zero, 1 @@ -15,17 +13,17 @@ RISCV_CODE = b"\x13\x05\x10\x00\x93\x85\x05\x02" # memory address where emulation starts -ADDRESS = 0x10000 +ADDRESS = 0x10000 # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) # Test RISCV @@ -59,8 +57,8 @@ def test_riscv(): a0 = mu.reg_read(UC_RISCV_REG_A0) a1 = mu.reg_read(UC_RISCV_REG_A1) - print(">>> A0 = 0x%x" %a0) - print(">>> A1 = 0x%x" %a1) + print(">>> A0 = 0x%x" % a0) + print(">>> A1 = 0x%x" % a1) except UcError as e: print("ERROR: %s" % e) @@ -68,4 +66,3 @@ def test_riscv(): if __name__ == '__main__': test_riscv() - diff --git a/bindings/python/sample_s390x.py b/bindings/python/tests/test_s390x.py similarity index 96% rename from bindings/python/sample_s390x.py rename to bindings/python/tests/test_s390x.py index 6b96d8270d..68798cd077 100644 --- a/bindings/python/sample_s390x.py +++ b/bindings/python/tests/test_s390x.py @@ -8,17 +8,17 @@ S390X_CODE = b"\x18\x23" # memory address where emulation starts -ADDRESS = 0x10000 +ADDRESS = 0x10000 # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) # Test RISCV @@ -60,4 +60,3 @@ def test_s390x(): if __name__ == '__main__': test_s390x() - diff --git a/bindings/python/shellcode.py b/bindings/python/tests/test_shellcode.py similarity index 82% rename from bindings/python/shellcode.py rename to bindings/python/tests/test_shellcode.py index 5c393e1f8c..1313f883ad 100755 --- a/bindings/python/shellcode.py +++ b/bindings/python/tests/test_shellcode.py @@ -4,11 +4,12 @@ # KaiJern Lau from __future__ import print_function +import pytest from unicorn import * from unicorn.x86_const import * # Original shellcode from this example. -#X86_CODE32 = b"\xeb\x19\x31\xc0\x31\xdb\x31\xd2\x31\xc9\xb0\x04\xb3\x01\x59\xb2\x05\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe2\xff\xff\xff\x68\x65\x6c\x6c\x6f" +# X86_CODE32 = b"\xeb\x19\x31\xc0\x31\xdb\x31\xd2\x31\xc9\xb0\x04\xb3\x01\x59\xb2\x05\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe2\xff\xff\xff\x68\x65\x6c\x6c\x6f" # Linux/x86 execve /bin/sh shellcode 23 bytes, from http://shell-storm.org/shellcode/files/shellcode-827.php # 0: 31 c0 xor eax,eax @@ -44,20 +45,22 @@ # memory address where emulation starts ADDRESS = 0x1000000 + # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) # read this instruction code from memory tmp = uc.mem_read(address, size) - print("*** PC = %x *** :" %(address), end="") + print("*** PC = %x *** :" % (address), end="") for i in tmp: - print(" %02x" %i, end="") + print(" %02x" % i, end="") print("") # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) + def read_string(uc, address): ret = "" @@ -70,21 +73,22 @@ def read_string(uc, address): read_bytes += 1 return ret + # callback for tracing Linux interrupt def hook_intr(uc, intno, user_data): # only handle Linux syscall if intno != 0x80: - print("got interrupt %x ???" %intno) + print("got interrupt %x ???" % intno) uc.emu_stop() return eax = uc.reg_read(UC_X86_REG_EAX) eip = uc.reg_read(UC_X86_REG_EIP) - if eax == 1: # sys_exit - print(">>> 0x%x: interrupt 0x%x, EAX = 0x%x" %(eip, intno, eax)) + if eax == 1: # sys_exit + print(">>> 0x%x: interrupt 0x%x, EAX = 0x%x" % (eip, intno, eax)) uc.emu_stop() - elif eax == 4: # sys_write + elif eax == 4: # sys_write # ECX = buffer address ecx = uc.reg_read(UC_X86_REG_ECX) # EDX = buffer size @@ -92,49 +96,53 @@ def hook_intr(uc, intno, user_data): try: buf = uc.mem_read(ecx, edx) print(">>> 0x%x: interrupt 0x%x, SYS_WRITE. buffer = 0x%x, size = %u, content = " \ - %(eip, intno, ecx, edx), end="") + % (eip, intno, ecx, edx), end="") for i in buf: - print("%c" %i, end="") + print("%c" % i, end="") print("") except UcError as e: print(">>> 0x%x: interrupt 0x%x, SYS_WRITE. buffer = 0x%x, size = %u, content = \n" \ - %(eip, intno, ecx, edx)) - elif eax == 11: # sys_write + % (eip, intno, ecx, edx)) + elif eax == 11: # sys_write ebx = uc.reg_read(UC_X86_REG_EBX) filename = read_string(uc, ebx) print(">>> SYS_EXECV filename=%s" % filename) else: - print(">>> 0x%x: interrupt 0x%x, EAX = 0x%x" %(eip, intno, eax)) + print(">>> 0x%x: interrupt 0x%x, EAX = 0x%x" % (eip, intno, eax)) def hook_syscall32(mu, user_data): eax = mu.reg_read(UC_X86_REG_EAX) - print(">>> got SYSCALL with EAX = 0x%x" %(eax)) + print(">>> got SYSCALL with EAX = 0x%x" % (eax)) mu.emu_stop() + def hook_syscall64(mu, user_data): rax = mu.reg_read(UC_X86_REG_RAX) rdi = mu.reg_read(UC_X86_REG_RDI) - print(">>> got SYSCALL with RAX = %d" %(rax)) - - if rax == 59: #sys_execve + print(">>> got SYSCALL with RAX = %d" % (rax)) + + if rax == 59: # sys_execve filename = read_string(mu, rdi) print(">>> SYS_EXECV filename=%s" % filename) else: rip = mu.reg_read(UC_X86_REG_RIP) - print(">>> Syscall Found at 0x%x: , RAX = 0x%x" %(rip, rax)) + print(">>> Syscall Found at 0x%x: , RAX = 0x%x" % (rip, rax)) mu.emu_stop() + +@pytest.mark.parametrize("mode,code", + [(UC_MODE_32, X86_CODE32_SELF), (UC_MODE_32, X86_CODE32), (UC_MODE_64, X86_CODE64)]) # Test X86 32 bit def test_i386(mode, code): if mode == UC_MODE_32: print("Emulate x86_32 code") elif mode == UC_MODE_64: print("Emulate x86_64 code") - + try: # Initialize emulator mu = Uc(UC_ARCH_X86, mode) @@ -171,9 +179,10 @@ def test_i386(mode, code): except UcError as e: print("ERROR: %s" % e) + if __name__ == '__main__': test_i386(UC_MODE_32, X86_CODE32_SELF) print("=" * 20) test_i386(UC_MODE_32, X86_CODE32) print("=" * 20) - test_i386(UC_MODE_64, X86_CODE64) \ No newline at end of file + test_i386(UC_MODE_64, X86_CODE64) diff --git a/bindings/python/sample_sparc.py b/bindings/python/tests/test_sparc.py similarity index 88% rename from bindings/python/sample_sparc.py rename to bindings/python/tests/test_sparc.py index 5dbe746ec9..badb7b76ff 100755 --- a/bindings/python/sample_sparc.py +++ b/bindings/python/tests/test_sparc.py @@ -6,21 +6,20 @@ from unicorn import * from unicorn.sparc_const import * - # code to be emulated -SPARC_CODE = b"\x86\x00\x40\x02" # add %g1, %g2, %g3; +SPARC_CODE = b"\x86\x00\x40\x02" # add %g1, %g2, %g3; # memory address where emulation starts -ADDRESS = 0x10000 +ADDRESS = 0x10000 # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) # Test SPARC @@ -28,7 +27,7 @@ def test_sparc(): print("Emulate SPARC code") try: # Initialize emulator in SPARC EB mode - mu = Uc(UC_ARCH_SPARC, UC_MODE_SPARC32|UC_MODE_BIG_ENDIAN) + mu = Uc(UC_ARCH_SPARC, UC_MODE_SPARC32 | UC_MODE_BIG_ENDIAN) # map 2MB memory for this emulation mu.mem_map(ADDRESS, 2 * 1024 * 1024) @@ -54,7 +53,7 @@ def test_sparc(): print(">>> Emulation done. Below is the CPU context") g3 = mu.reg_read(UC_SPARC_REG_G3) - print(">>> G3 = 0x%x" %g3) + print(">>> G3 = 0x%x" % g3) except UcError as e: print("ERROR: %s" % e) diff --git a/bindings/python/sample_tricore.py b/bindings/python/tests/test_tricore.py similarity index 89% rename from bindings/python/sample_tricore.py rename to bindings/python/tests/test_tricore.py index 2e7174e1f7..20bbc03a5b 100755 --- a/bindings/python/sample_tricore.py +++ b/bindings/python/tests/test_tricore.py @@ -1,26 +1,29 @@ #!/usr/bin/env python -''' +""" Created for Unicorn Engine by Eric Poole , 2022 Copyright 2022 Aptiv -''' +""" from __future__ import print_function from unicorn import * from unicorn.tricore_const import * # code to be emulated -TRICORE_CODE = b"\x82\x11\xbb\x00\x00\x08" # mov d0, #0x1; mov.u d0, #0x8000 +TRICORE_CODE = b"\x82\x11\xbb\x00\x00\x08" # mov d0, #0x1; mov.u d0, #0x8000 # memory address where emulation starts -ADDRESS = 0x10000 +ADDRESS = 0x10000 + # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) + # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) + # Test TriCore def test_tricore(): @@ -48,10 +51,11 @@ def test_tricore(): print(">>> Emulation done. Below is the CPU context") r0 = mu.reg_read(UC_TRICORE_REG_D0) - print(">>> D0 = 0x%x" %r0) + print(">>> D0 = 0x%x" % r0) except UcError as e: print("ERROR: %s" % e) + if __name__ == '__main__': test_tricore() diff --git a/bindings/python/sample_x86.py b/bindings/python/tests/test_x86.py similarity index 85% rename from bindings/python/sample_x86.py rename to bindings/python/tests/test_x86.py index 36dfe735fa..f8edce06b0 100755 --- a/bindings/python/sample_x86.py +++ b/bindings/python/tests/test_x86.py @@ -2,21 +2,21 @@ # Sample code for X86 of Unicorn. Nguyen Anh Quynh from __future__ import print_function +import pickle from unicorn import * from unicorn.x86_const import * -import pickle -X86_CODE32 = b"\x41\x4a\x66\x0f\xef\xc1" # INC ecx; DEC edx; PXOR xmm0, xmm1 -X86_CODE32_LOOP = b"\x41\x4a\xeb\xfe" # INC ecx; DEC edx; JMP self-loop -X86_CODE32_JUMP = b"\xeb\x02\x90\x90\x90\x90\x90\x90" # jmp 4; nop; nop; nop; nop; nop; nop -X86_CODE32_JMP_INVALID = b"\xe9\xe9\xee\xee\xee\x41\x4a" # JMP outside; INC ecx; DEC edx -X86_CODE32_MEM_READ = b"\x8B\x0D\xAA\xAA\xAA\xAA\x41\x4a" # mov ecx,[0xaaaaaaaa]; INC ecx; DEC edx -X86_CODE32_MEM_WRITE = b"\x89\x0D\xAA\xAA\xAA\xAA\x41\x4a" # mov [0xaaaaaaaa], ecx; INC ecx; DEC edx +X86_CODE32 = b"\x41\x4a\x66\x0f\xef\xc1" # INC ecx; DEC edx; PXOR xmm0, xmm1 +X86_CODE32_LOOP = b"\x41\x4a\xeb\xfe" # INC ecx; DEC edx; JMP self-loop +X86_CODE32_JUMP = b"\xeb\x02\x90\x90\x90\x90\x90\x90" # jmp 4; nop; nop; nop; nop; nop; nop +X86_CODE32_JMP_INVALID = b"\xe9\xe9\xee\xee\xee\x41\x4a" # JMP outside; INC ecx; DEC edx +X86_CODE32_MEM_READ = b"\x8B\x0D\xAA\xAA\xAA\xAA\x41\x4a" # mov ecx,[0xaaaaaaaa]; INC ecx; DEC edx +X86_CODE32_MEM_WRITE = b"\x89\x0D\xAA\xAA\xAA\xAA\x41\x4a" # mov [0xaaaaaaaa], ecx; INC ecx; DEC edx X86_CODE64 = b"\x41\xBC\x3B\xB0\x28\x2A\x49\x0F\xC9\x90\x4D\x0F\xAD\xCF\x49\x87\xFD\x90\x48\x81\xD2\x8A\xCE\x77\x35\x48\xF7\xD9\x4D\x29\xF4\x49\x81\xC9\xF6\x8A\xC6\x53\x4D\x87\xED\x48\x0F\xAD\xD2\x49\xF7\xD4\x48\xF7\xE1\x4D\x19\xC5\x4D\x89\xC5\x48\xF7\xD6\x41\xB8\x4F\x8D\x6B\x59\x4D\x87\xD0\x68\x6A\x1E\x09\x3C\x59" -X86_CODE32_INOUT = b"\x41\xE4\x3F\x4a\xE6\x46\x43" # INC ecx; IN AL, 0x3f; DEC edx; OUT 0x46, AL; INC ebx -X86_CODE64_SYSCALL = b'\x0f\x05' # SYSCALL -X86_CODE16 = b'\x00\x00' # add byte ptr [bx + si], al -X86_MMIO_CODE = b"\x89\x0d\x04\x00\x02\x00\x8b\x0d\x04\x00\x02\x00" # mov [0x20004], ecx; mov ecx, [0x20004] +X86_CODE32_INOUT = b"\x41\xE4\x3F\x4a\xE6\x46\x43" # INC ecx; IN AL, 0x3f; DEC edx; OUT 0x46, AL; INC ebx +X86_CODE64_SYSCALL = b'\x0f\x05' # SYSCALL +X86_CODE16 = b'\x00\x00' # add byte ptr [bx + si], al +X86_MMIO_CODE = b"\x89\x0d\x04\x00\x02\x00\x8b\x0d\x04\x00\x02\x00" # mov [0x20004], ecx; mov ecx, [0x20004] # memory address where emulation starts ADDRESS = 0x1000000 @@ -24,28 +24,29 @@ # callback for tracing basic blocks def hook_block(uc, address, size, user_data): - print(">>> Tracing basic block at 0x%x, block size = 0x%x" %(address, size)) + print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) # callback for tracing instructions def hook_code(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) eflags = uc.reg_read(UC_X86_REG_EFLAGS) - print(">>> --- EFLAGS is 0x%x" %eflags) + print(">>> --- EFLAGS is 0x%x" % eflags) + def hook_code64(uc, address, size, user_data): - print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size)) + print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) rip = uc.reg_read(UC_X86_REG_RIP) - print(">>> RIP is 0x%x" %rip) + print(">>> RIP is 0x%x" % rip) # callback for tracing invalid memory access (READ or WRITE) def hook_mem_invalid(uc, access, address, size, value, user_data): if access == UC_MEM_WRITE_UNMAPPED: print(">>> Missing memory is being WRITE at 0x%x, data size = %u, data value = 0x%x" \ - %(address, size, value)) + % (address, size, value)) # map this memory in with 2MB in size - uc.mem_map(0xaaaa0000, 2 * 1024*1024) + uc.mem_map(0xaaaa0000, 2 * 1024 * 1024) # return True to indicate we want to continue emulation return True else: @@ -57,16 +58,16 @@ def hook_mem_invalid(uc, access, address, size, value, user_data): def hook_mem_access(uc, access, address, size, value, user_data): if access == UC_MEM_WRITE: print(">>> Memory is being WRITE at 0x%x, data size = %u, data value = 0x%x" \ - %(address, size, value)) - else: # READ + % (address, size, value)) + else: # READ print(">>> Memory is being READ at 0x%x, data size = %u" \ - %(address, size)) + % (address, size)) # callback for IN instruction def hook_in(uc, port, size, user_data): eip = uc.reg_read(UC_X86_REG_EIP) - print("--- reading from port 0x%x, size: %u, address: 0x%x" %(port, size, eip)) + print("--- reading from port 0x%x, size: %u, address: 0x%x" % (port, size, eip)) if size == 1: # read 1 byte to AL return 0xf1 @@ -83,7 +84,7 @@ def hook_in(uc, port, size, user_data): # callback for OUT instruction def hook_out(uc, port, size, value, user_data): eip = uc.reg_read(UC_X86_REG_EIP) - print("--- writing to port 0x%x, size: %u, value: 0x%x, address: 0x%x" %(port, size, value, eip)) + print("--- writing to port 0x%x, size: %u, value: 0x%x, address: 0x%x" % (port, size, value, eip)) # confirm that value is indeed the value of AL/AX/EAX v = 0 @@ -97,7 +98,7 @@ def hook_out(uc, port, size, value, user_data): # read 4 bytes in EAX v = uc.reg_read(UC_X86_REG_EAX) - print("--- register value = 0x%x" %v) + print("--- register value = 0x%x" % v) # Test X86 32 bit @@ -134,15 +135,15 @@ def test_i386(): r_ecx = mu.reg_read(UC_X86_REG_ECX) r_edx = mu.reg_read(UC_X86_REG_EDX) r_xmm0 = mu.reg_read(UC_X86_REG_XMM0) - print(">>> ECX = 0x%x" %r_ecx) - print(">>> EDX = 0x%x" %r_edx) - print(">>> XMM0 = 0x%.32x" %r_xmm0) + print(">>> ECX = 0x%x" % r_ecx) + print(">>> EDX = 0x%x" % r_edx) + print(">>> XMM0 = 0x%.32x" % r_xmm0) # read from memory tmp = mu.mem_read(ADDRESS, 4) - print(">>> Read 4 bytes from [0x%x] = 0x" %(ADDRESS), end="") + print(">>> Read 4 bytes from [0x%x] = 0x" % (ADDRESS), end="") for i in reversed(tmp): - print("%x" %(i), end="") + print("%x" % (i), end="") print("") except UcError as e: @@ -179,14 +180,14 @@ def test_i386_map_ptr(): r_ecx = mu.reg_read(UC_X86_REG_ECX) r_edx = mu.reg_read(UC_X86_REG_EDX) - print(">>> ECX = 0x%x" %r_ecx) - print(">>> EDX = 0x%x" %r_edx) + print(">>> ECX = 0x%x" % r_ecx) + print(">>> EDX = 0x%x" % r_edx) # read from memory tmp = mu.mem_read(ADDRESS, 4) - print(">>> Read 4 bytes from [0x%x] = 0x" %(ADDRESS), end="") + print(">>> Read 4 bytes from [0x%x] = 0x" % (ADDRESS), end="") for i in reversed(tmp): - print("%x" %(i), end="") + print("%x" % (i), end="") print("") except UcError as e: @@ -226,12 +227,13 @@ def test_i386_invalid_mem_read(): r_ecx = mu.reg_read(UC_X86_REG_ECX) r_edx = mu.reg_read(UC_X86_REG_EDX) - print(">>> ECX = 0x%x" %r_ecx) - print(">>> EDX = 0x%x" %r_edx) + print(">>> ECX = 0x%x" % r_ecx) + print(">>> EDX = 0x%x" % r_edx) except UcError as e: print("ERROR: %s" % e) + def test_i386_jump(): print("Emulate i386 code with jump") try: @@ -298,22 +300,22 @@ def test_i386_invalid_mem_write(): r_ecx = mu.reg_read(UC_X86_REG_ECX) r_edx = mu.reg_read(UC_X86_REG_EDX) - print(">>> ECX = 0x%x" %r_ecx) - print(">>> EDX = 0x%x" %r_edx) + print(">>> ECX = 0x%x" % r_ecx) + print(">>> EDX = 0x%x" % r_edx) # read from memory - print(">>> Read 4 bytes from [0x%x] = 0x" %(0xaaaaaaaa), end="") + print(">>> Read 4 bytes from [0x%x] = 0x" % (0xaaaaaaaa), end="") tmp = mu.mem_read(0xaaaaaaaa, 4) for i in reversed(tmp): if i != 0: - print("%x" %i, end="") + print("%x" % i, end="") print("") try: tmp = mu.mem_read(0xffffffaa, 4) - print(">>> Read 4 bytes from [0x%x] = 0x" %(0xffffffaa), end="") + print(">>> Read 4 bytes from [0x%x] = 0x" % (0xffffffaa), end="") for i in reversed(tmp): - print("%x" %i, end="") + print("%x" % i, end="") print("") except UcError as e: @@ -322,6 +324,7 @@ def test_i386_invalid_mem_write(): except UcError as e: print("ERROR: %s" % e) + def test_i386_jump_invalid(): print("Emulate i386 code that jumps to invalid memory") try: @@ -347,18 +350,19 @@ def test_i386_jump_invalid(): try: mu.emu_start(ADDRESS, ADDRESS + len(X86_CODE32_JMP_INVALID)) except UcError as e: - print("Failed on uc_emu_start() with error returned 8: %s" %e) + print("Failed on uc_emu_start() with error returned 8: %s" % e) print(">>> Emulation done. Below is the CPU context") r_ecx = mu.reg_read(UC_X86_REG_ECX) r_edx = mu.reg_read(UC_X86_REG_EDX) - print(">>> ECX = 0x%x" %r_ecx) - print(">>> EDX = 0x%x" %r_edx) + print(">>> ECX = 0x%x" % r_ecx) + print(">>> EDX = 0x%x" % r_edx) except UcError as e: print("ERROR %s" % e) + def test_i386_loop(): print("Emulate i386 code that loop forever") try: @@ -375,18 +379,19 @@ def test_i386_loop(): mu.reg_write(UC_X86_REG_ECX, 0x1234) mu.reg_write(UC_X86_REG_EDX, 0x7890) - mu.emu_start(ADDRESS, ADDRESS + len(X86_CODE32_LOOP), timeout=2*UC_SECOND_SCALE) + mu.emu_start(ADDRESS, ADDRESS + len(X86_CODE32_LOOP), timeout=2 * UC_SECOND_SCALE) print(">>> Emulation done. Below is the CPU context") r_ecx = mu.reg_read(UC_X86_REG_ECX) r_edx = mu.reg_read(UC_X86_REG_EDX) - print(">>> ECX = 0x%x" %r_ecx) - print(">>> EDX = 0x%x" %r_edx) + print(">>> ECX = 0x%x" % r_ecx) + print(">>> EDX = 0x%x" % r_edx) except UcError as e: print("ERROR: %s" % e) + # Test X86 32 bit with IN/OUT instruction def test_i386_inout(): print("Emulate i386 code with IN/OUT instructions") @@ -422,8 +427,8 @@ def test_i386_inout(): r_ecx = mu.reg_read(UC_X86_REG_ECX) r_eax = mu.reg_read(UC_X86_REG_EAX) - print(">>> EAX = 0x%x" %r_eax) - print(">>> ECX = 0x%x" %r_ecx) + print(">>> EAX = 0x%x" % r_eax) + print(">>> ECX = 0x%x" % r_ecx) except UcError as e: print("ERROR: %s" % e) @@ -446,10 +451,10 @@ def test_i386_context_save(): mu.reg_write(UC_X86_REG_EAX, 1) print(">>> Running emulation for the first time") - mu.emu_start(address, address+1) + mu.emu_start(address, address + 1) print(">>> Emulation done. Below is the CPU context") - print(">>> EAX = 0x%x" %(mu.reg_read(UC_X86_REG_EAX))) + print(">>> EAX = 0x%x" % (mu.reg_read(UC_X86_REG_EAX))) print(">>> Saving CPU context") saved_context = mu.context_save() @@ -457,9 +462,9 @@ def test_i386_context_save(): pickled_saved_context = pickle.dumps(saved_context) print(">>> Running emulation for the second time") - mu.emu_start(address, address+1) + mu.emu_start(address, address + 1) print(">>> Emulation done. Below is the CPU context") - print(">>> EAX = 0x%x" %(mu.reg_read(UC_X86_REG_EAX))) + print(">>> EAX = 0x%x" % (mu.reg_read(UC_X86_REG_EAX))) print(">>> Unpickling CPU context") saved_context = pickle.loads(pickled_saved_context) @@ -469,11 +474,12 @@ def test_i386_context_save(): print(">>> CPU context restored. Below is the CPU context") mu.context_restore(saved_context) - print(">>> EAX = 0x%x" %(mu.reg_read(UC_X86_REG_EAX))) + print(">>> EAX = 0x%x" % (mu.reg_read(UC_X86_REG_EAX))) except UcError as e: print("ERROR: %s" % e) + def test_x86_64(): print("Emulate x86_64 code") try: @@ -509,13 +515,13 @@ def test_x86_64(): mu.hook_add(UC_HOOK_BLOCK, hook_block) # tracing all instructions in range [ADDRESS, ADDRESS+20] - mu.hook_add(UC_HOOK_CODE, hook_code64, None, ADDRESS, ADDRESS+20) + mu.hook_add(UC_HOOK_CODE, hook_code64, None, ADDRESS, ADDRESS + 20) # tracing all memory READ & WRITE access mu.hook_add(UC_HOOK_MEM_WRITE, hook_mem_access) mu.hook_add(UC_HOOK_MEM_READ, hook_mem_access) # actually you can also use READ_WRITE to trace all memory access - #mu.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, hook_mem_access) + # mu.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, hook_mem_access) try: # emulate machine code in infinite time @@ -541,20 +547,20 @@ def test_x86_64(): r14 = mu.reg_read(UC_X86_REG_R14) r15 = mu.reg_read(UC_X86_REG_R15) - print(">>> RAX = 0x%x" %rax) - print(">>> RBX = 0x%x" %rbx) - print(">>> RCX = 0x%x" %rcx) - print(">>> RDX = 0x%x" %rdx) - print(">>> RSI = 0x%x" %rsi) - print(">>> RDI = 0x%x" %rdi) - print(">>> R8 = 0x%x" %r8) - print(">>> R9 = 0x%x" %r9) - print(">>> R10 = 0x%x" %r10) - print(">>> R11 = 0x%x" %r11) - print(">>> R12 = 0x%x" %r12) - print(">>> R13 = 0x%x" %r13) - print(">>> R14 = 0x%x" %r14) - print(">>> R15 = 0x%x" %r15) + print(">>> RAX = 0x%x" % rax) + print(">>> RBX = 0x%x" % rbx) + print(">>> RCX = 0x%x" % rcx) + print(">>> RDX = 0x%x" % rdx) + print(">>> RSI = 0x%x" % rsi) + print(">>> RDI = 0x%x" % rdi) + print(">>> R8 = 0x%x" % r8) + print(">>> R9 = 0x%x" % r9) + print(">>> R10 = 0x%x" % r10) + print(">>> R11 = 0x%x" % r11) + print(">>> R12 = 0x%x" % r12) + print(">>> R13 = 0x%x" % r13) + print(">>> R14 = 0x%x" % r14) + print(">>> R15 = 0x%x" % r15) except UcError as e: @@ -626,19 +632,22 @@ def test_x86_16(): print(">>> Emulation done. Below is the CPU context") tmp = mu.mem_read(11, 1) - print(">>> Read 1 bytes from [0x%x] = 0x%x" %(11, tmp[0])) + print(">>> Read 1 bytes from [0x%x] = 0x%x" % (11, tmp[0])) except UcError as e: print("ERROR: %s" % e) + def mmio_read_cb(uc, offset, size, data): print(f">>> Read IO memory at offset {hex(offset)} with {hex(size)} bytes and return 0x19260817") return 0x19260817 + def mmio_write_cb(uc, offset, size, value, data): print(f">>> Write value {hex(value)} to IO memory at offset {hex(offset)} with {hex(size)} bytes") + def test_i386_mmio(): print("Test i386 IO memory") try: @@ -664,6 +673,7 @@ def test_i386_mmio(): except UcError as e: print("ERROR: %s" % e) + if __name__ == '__main__': test_x86_16() test_i386()