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 <mio@lazym.io>
This commit is contained in:
@Antelox 2024-10-17 13:35:42 +02:00 committed by GitHub
parent c42cc0fe86
commit 6fbbf3089a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 1192 additions and 1051 deletions

View File

@ -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 }}

File diff suppressed because it is too large Load Diff

View File

@ -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 }}

8
.gitignore vendored
View File

@ -90,4 +90,10 @@ packages/
cmocka/
zig-cache/
zig-out/
.cache
.cache
##################
## PyCharm Project
##################
.idea/

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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/

View File

@ -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*"]

View File

@ -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

View File

@ -1,2 +0,0 @@
[bdist_wheel]
universal=1

View File

@ -1,29 +1,19 @@
#!/usr/bin/env python
# Python binding for Unicorn engine. Nguyen Anh Quynh <aquynh@gmail.com>
from __future__ import print_function
import glob
import logging
import os
import subprocess
import shutil
import sys
import platform
import setuptools
import shutil
import subprocess
import sys
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 @@ SRC_DIR = os.path.join(ROOT_DIR, 'src')
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)
return super().run()
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('-', '_'))
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
)

View File

@ -6,22 +6,21 @@ from __future__ import print_function
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)

View File

@ -6,25 +6,24 @@ from __future__ import print_function
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 test_arm64_hook_mrs():
except UcError as e:
print("ERROR: %s" % e)
if __name__ == '__main__':
test_arm64()
print("=" * 26)

View File

@ -7,22 +7,21 @@ from __future__ import print_function
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)

View File

@ -5,22 +5,21 @@ from __future__ import print_function
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)

View File

@ -6,6 +6,7 @@ from unicorn import *
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()
print("=" * 32)
test_uc_ctl_exits()

View File

@ -6,21 +6,20 @@ from __future__ import print_function
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

View File

@ -6,23 +6,22 @@ from __future__ import print_function
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)

View File

@ -3,10 +3,11 @@
# Nguyen Tan Cong <shenlongbk@gmail.com>
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

View File

@ -1,26 +1,24 @@
#!/usr/bin/env python
# Sample code for PPC of Unicorn. Nguyen Anh Quynh <aquynh@gmail.com>
#
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()

View File

@ -1,12 +1,10 @@
#!/usr/bin/env python
# Sample code for RISCV of Unicorn. Nguyen Anh Quynh <aquynh@gmail.com>
#
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 @@ $ cstool riscv64 1305100093850502
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()

View File

@ -8,17 +8,17 @@ from unicorn.s390x_const import *
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()

View File

@ -4,11 +4,12 @@
# KaiJern Lau <kj@theshepherdlab.io>
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 @@ X86_CODE64 = b"\x48\x31\xff\x57\x57\x5e\x5a\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\
# 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 = <unknown>\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)
test_i386(UC_MODE_64, X86_CODE64)

View File

@ -6,21 +6,20 @@ from __future__ import print_function
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)

View File

@ -1,26 +1,29 @@
#!/usr/bin/env python
'''
"""
Created for Unicorn Engine by Eric Poole <eric.poole@aptiv.com>, 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()

View File

@ -2,21 +2,21 @@
# Sample code for X86 of Unicorn. Nguyen Anh Quynh <aquynh@gmail.com>
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 @@ ADDRESS = 0x1000000
# 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()