From 4fb4b3e4b047a6aac243fecf4130620d4c62a773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20C=2E=20Fran=C3=A7a?= Date: Fri, 31 Mar 2023 13:31:02 -0300 Subject: [PATCH] Zig binding * zig binding - sample added * zig build CI * split mingw (shared/static) CI/CD * unicorn log added * build C/C++ samples --- .github/workflows/build-uc2.yml | 7 +- .github/workflows/zigbuild.yml | 81 ++++++ .gitignore | 2 + CMakeLists.txt | 7 +- bindings/python/setup.py | 2 +- bindings/zig/sample/sample_riscv_zig.zig | 218 ++++++++++++++++ bindings/zig/tools/zigcc.cmd | 3 + bindings/zig/tools/zigcc.sh | 3 + bindings/zig/unicorn/unicorn.zig | 193 +++++++++++++- build.zig | 244 ++++++++++++++++++ .../bundle_static.cmake | 0 mingw-w64.cmake => cmake/mingw-w64.cmake | 0 cmake/zig.cmake | 9 + 13 files changed, 762 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/zigbuild.yml create mode 100644 bindings/zig/sample/sample_riscv_zig.zig create mode 100644 bindings/zig/tools/zigcc.cmd create mode 100755 bindings/zig/tools/zigcc.sh create mode 100644 build.zig rename bundle_static.cmake => cmake/bundle_static.cmake (100%) rename mingw-w64.cmake => cmake/mingw-w64.cmake (100%) create mode 100644 cmake/zig.cmake diff --git a/.github/workflows/build-uc2.yml b/.github/workflows/build-uc2.yml index ded889ef..c986bcc4 100644 --- a/.github/workflows/build-uc2.yml +++ b/.github/workflows/build-uc2.yml @@ -35,7 +35,7 @@ jobs: shared: 'yes', mingw: MINGW64, mingw-arch: x86_64, - artifact: 'windows_mingw64.7z', + artifact: 'windows_mingw64-shared.7z', build_type: 'Debug', archiver: '7z a', generators: 'Ninja' @@ -49,7 +49,7 @@ jobs: shared: 'no', mingw: MINGW64, mingw-arch: x86_64, - artifact: 'windows_mingw64.7z', + artifact: 'windows_mingw64-static.7z', build_type: 'Debug', archiver: '7z a', generators: 'Ninja' @@ -217,7 +217,7 @@ jobs: #export CC=i686-w64-mingw32-gcc export AR=gcc-ar export RANLIB=gcc-ranlib - export CFLAGS="-m32" + export CFLAGS="-m32 -static" export LDFLAGS="-m32" export LDFLAGS_STATIC="-m32" export UNICORN_QEMU_FLAGS="--cpu=i386" @@ -230,6 +230,7 @@ jobs: -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 diff --git a/.github/workflows/zigbuild.yml b/.github/workflows/zigbuild.yml new file mode 100644 index 00000000..8be1b65d --- /dev/null +++ b/.github/workflows/zigbuild.yml @@ -0,0 +1,81 @@ +name: Zig Build + +on: + push: + paths-ignore: + - ".gitignore" + - "docs/**" + - "README" + - "CREDITS.TXT" + - "COPYING_GLIB" + - "COPYING.LGPL2" + - "AUTHORS.TXT" + - "CHANGELOG" + - "COPYING" + pull_request: + +jobs: + build: + strategy: + fail-fast: false + matrix: + runs-on: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.runs-on }} + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 + - uses: goto-bus-stop/setup-zig@v2 + with: + version: 0.11.0 + - uses: lukka/get-cmake@latest + with: + cmakeVersion: latest + ninjaVersion: latest + + - name: CMake Build + run: zig build cmake + + - name: Build Summary + run: zig build --summary all -freference-trace + +# =================================================================== + # zig-mingw: + # runs-on: windows-latest + # strategy: + # fail-fast: false + # matrix: + # include: [{ msystem: CLANG64, arch: x86_64, prefix: /clang64 }, { msystem: CLANG32, arch: i686, prefix: /clang32 }, { msystem: CLANGARM64, arch: aarch64, prefix: /clangarm64 }] + # steps: + # - uses: actions/checkout@v3 + # with: + # path: temp + # submodules: recursive + # fetch-depth: 0 + # - uses: goto-bus-stop/setup-zig@v2 + # with: + # version: master + # - uses: msys2/setup-msys2@v2 + # with: + # msystem: ${{ matrix.msystem }} + # path-type: inherit + # location: D:\ + # install: git mingw-w64-clang-${{ matrix.arch }}-cmake + # update: true + + # - name: Move Checkout + # run: | + # Copy-Item -Path ".\temp" -Destination "C:\_" -Recurse + + # - name: Build Summary - ${{ matrix.arch }} + # shell: msys2 {0} + # run: | + # cd /C/_ + # zig build cmake + # if [${{ matrix.config.arch }} == 'i686' ]; then + # zig build --summary all -freference-trace -Dtarget=x86-windows + # else + # zig build --summary all -freference-trace -Dtarget=${{ matrix.arch }}-windows + # fi + \ No newline at end of file diff --git a/.gitignore b/.gitignore index ced6f437..4b386948 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,5 @@ rust_build [Oo]bj/ packages/ cmocka/ +zig-cache/ +zig-out/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e467090..503bfb75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,11 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.15") cmake_policy(SET CMP0092 NEW) endif() +option(ZIG_BUILD "Enable zig build" OFF) +if(ZIG_BUILD) + include(cmake/zig.cmake) +endif() + # Workaround to fix wrong compiler on macos. if(APPLE AND NOT CMAKE_C_COMPILER) set(CMAKE_C_COMPILER "/usr/bin/cc") @@ -47,7 +52,7 @@ set(UNICORN_VERSION_MAJOR 2) set(UNICORN_VERSION_MINOR 0) set(UNICORN_VERSION_PATCH 2) -include(bundle_static.cmake) +include(cmake/bundle_static.cmake) # Even though we generate shared lib and static archive at the same time, we still support # using unicorn as a subdirectory so we have to respect BUILD_SHARED_LIBS. diff --git a/bindings/python/setup.py b/bindings/python/setup.py index b8cfbdfa..7df50621 100755 --- a/bindings/python/setup.py +++ b/bindings/python/setup.py @@ -73,7 +73,7 @@ def copy_sources(): 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, "../../*.cmake"))) + src.extend(glob.glob(os.path.join(ROOT_DIR, "../../cmake/*.cmake"))) src.extend(glob.glob(os.path.join(ROOT_DIR, "../../LICENSE*"))) src.extend(glob.glob(os.path.join(ROOT_DIR, "../../README.md"))) diff --git a/bindings/zig/sample/sample_riscv_zig.zig b/bindings/zig/sample/sample_riscv_zig.zig new file mode 100644 index 00000000..ee372ade --- /dev/null +++ b/bindings/zig/sample/sample_riscv_zig.zig @@ -0,0 +1,218 @@ +//! Based on: ../../../samples/sample_riscv.c + +const unicorn = @import("unicorn"); +const unicornC = unicorn.c; +const log = unicorn.log; + +const RISCV_CODE = "\x13\x05\x10\x00\x93\x85\x05\x02"; +const ADDRESS = 0x10000; + +pub fn main() !void { + try test_recover_from_illegal(); + log.info("------------------", .{}); + try test_riscv2(); + log.info("------------------", .{}); + try test_riscv_func_return(); +} + +fn hook_block(uc: ?*unicornC.uc_engine, address: u64, size: u32, user_data: ?*anyopaque) callconv(.C) void { + _ = user_data; + _ = uc; + log.info(">>> Tracing basic block at 0x{}, block size = 0x{}", .{ address, size }); +} + +fn hook_code(uc: ?*unicornC.uc_engine, address: u64, size: u32, user_data: ?*anyopaque) callconv(.C) void { + _ = user_data; + _ = uc; + log.info(">>> Tracing instruction at 0x{}, instruction size = 0x{}", .{ address, size }); +} + +fn hook_code3(uc: ?*unicornC.uc_engine, address: u64, size: u32, user_data: ?*anyopaque) callconv(.C) void { + _ = user_data; + log.info(">>> Tracing instruction at 0x{}, instruction size = 0x{}", .{ address, size }); + if (address == ADDRESS) { + log.info("stop emulation"); + unicorn.uc_emu_stop(uc) catch |err| log.err("Error: {}", .{err}); + } +} +fn hook_memalloc(uc: ?*unicornC.uc_engine, @"type": unicornC.uc_mem_type, address: u64, size: u32, user_data: ?*anyopaque) callconv(.C) bool { + _ = user_data; + _ = @"type"; + var algined_address = address & 0xFFFFFFFFFFFFF000; + var aligned_size = (@as(u32, @intCast(size / 0x1000)) + 1) * 0x1000; + + log.info(">>> Allocating block at 0x{} (0x{}), block size = 0x{} (0x{})", .{ address, algined_address, size, aligned_size }); + + unicorn.uc_mem_map(uc, algined_address, aligned_size, unicornC.UC_PROT_ALL) catch |err| log.err("Error: {}", .{err}); + + // this recovers from missing memory, so we return true + return true; +} + +fn test_recover_from_illegal() !void { + var uc: ?*unicornC.uc_engine = null; + var trace1: unicornC.uc_hook = undefined; + var trace2: unicornC.uc_hook = undefined; + var mem_alloc: unicornC.uc_hook = undefined; + var a0: u64 = 0x1234; + var a1: u64 = 0x7890; + + log.info("Emulate RISCV code: recover_from_illegal", .{}); + + // Initialize emulator in RISCV64 mode + unicorn.uc_open(unicornC.UC_ARCH_RISCV, unicornC.UC_MODE_RISCV64, &uc) catch |err| { + log.err("Failed on uc_open() with error returned: {}", .{err}); + return; + }; + + try unicorn.uc_reg_write(uc, unicornC.UC_RISCV_REG_A0, &a0); + try unicorn.uc_reg_write(uc, unicornC.UC_RISCV_REG_A1, &a1); + + // map 2MB memory for this emulation + try unicorn.uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, unicornC.UC_PROT_ALL); + + // auto-allocate memory on access + try unicorn.uc_hook_add(uc, &mem_alloc, unicornC.UC_HOOK_MEM_UNMAPPED, @as(?*anyopaque, @ptrCast(@constCast(&hook_memalloc))), null, 1, 0); + + // tracing all basic blocks with customized callback + try unicorn.uc_hook_add(uc, &trace1, unicornC.UC_HOOK_BLOCK, @as(?*anyopaque, @ptrCast(@constCast(&hook_block))), null, 1, 0); + + // tracing all instruction + try unicorn.uc_hook_add(uc, &trace2, unicornC.UC_HOOK_CODE, @as(?*anyopaque, @ptrCast(@constCast(&hook_code))), null, 1, 0); + + // write machine code to be emulated to memory + try unicorn.uc_mem_write(uc, ADDRESS, RISCV_CODE, RISCV_CODE.len - 1); + + // emulate 1 instruction, wrong address, illegal code + unicorn.uc_emu_start(uc, 0x1000, @as(u64, @bitCast(@as(i64, -1))), 0, 1) catch |err| + log.err("Expected Illegal Instruction error, got: {} ({s})", .{ err, unicorn.uc_strerror(err) }); + + // emulate 1 instruction, correct address, valid code + unicorn.uc_emu_start(uc, ADDRESS, @as(u64, @bitCast(@as(i64, -1))), 0, 1) catch |err| + log.err("Failed on uc_emu_start() with error returned: {}", .{err}); + + // now print out some registers + log.info(">>> Emulation done. Below is the CPU context", .{}); + + try unicorn.uc_reg_read(uc, unicornC.UC_RISCV_REG_A0, @as(?*anyopaque, @ptrCast(@constCast(&a0)))); + try unicorn.uc_reg_read(uc, unicornC.UC_RISCV_REG_A1, @as(?*anyopaque, @ptrCast(@constCast(&a1)))); + + log.info(">>> A0 = 0x{}", .{a0}); + log.info(">>> A1 = 0x{}", .{a1}); + + try unicorn.uc_close(uc); +} + +fn test_riscv_func_return() !void { + var uc: ?*unicornC.uc_engine = null; + var trace1: unicornC.uc_hook = undefined; + var trace2: unicornC.uc_hook = undefined; + var pc: u64 = 0; + var ra: u64 = 0; + + const CODE = "\x67\x80\x00\x00\x82\x80\x01\x00\x01\x00"; + + log.info("Emulate RISCV code: return from func", .{}); + + // Initialize emulator in RISCV64 mode + unicorn.uc_open(unicornC.UC_ARCH_RISCV, unicornC.UC_MODE_RISCV64, &uc) catch |err| { + log.err("Failed on uc_open() with error returned: {} ({s})", .{ err, unicorn.uc_strerror(err) }); + return; + }; + + // map 2MB memory for this emulation + try unicorn.uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, unicornC.UC_PROT_ALL); + + // write machine code to be emulated to memory + try unicorn.uc_mem_write(uc, ADDRESS, CODE, CODE.len - 1); + + // tracing all basic blocks with customized callback + try unicorn.uc_hook_add(uc, &trace1, unicornC.UC_HOOK_BLOCK, @as(?*anyopaque, @ptrCast(@constCast(&hook_block))), null, 1, 0); + + // tracing all instruction + try unicorn.uc_hook_add(uc, &trace2, unicornC.UC_HOOK_CODE, @as(?*anyopaque, @ptrCast(@constCast(&hook_code))), null, 1, 0); + + ra = 0x10006; + try unicorn.uc_reg_write(uc, unicornC.UC_RISCV_REG_RA, @as(?*anyopaque, @ptrCast(@constCast(&ra)))); + + log.info("========", .{}); + // execute c.ret instruction + unicorn.uc_emu_start(uc, 0x10004, @as(u64, @bitCast(@as(i64, -1))), 0, 1) catch |err| { + log.err("Failed on uc_emu_start() with error returned: {}", .{err}); + }; + + try unicorn.uc_reg_read(uc, unicornC.UC_RISCV_REG_PC, @as(?*anyopaque, @ptrCast(@constCast(&pc)))); + if (pc != ra) { + log.info("Error after execution: PC is: 0x{}, expected was 0x{}", .{ pc, ra }); + if (pc == 0x10004) { + log.info(" PC did not change during execution", .{}); + } + } else { + log.info("Good, PC == RA", .{}); + } + + // now print out some registers + log.info(">>> Emulation done.", .{}); + + try unicorn.uc_close(uc); +} + +fn test_riscv2() !void { + var uc: ?*unicornC.uc_engine = null; + var trace1: unicornC.uc_hook = undefined; + var trace2: unicornC.uc_hook = undefined; + + var a0: u32 = 0x1234; + var a1: u32 = 0x7890; + + log.info("Emulate RISCV code: split emulation", .{}); + + // Initialize emulator in RISCV64 mode + unicorn.uc_open(unicornC.UC_ARCH_RISCV, unicornC.UC_MODE_RISCV32, &uc) catch |err| { + log.err("Failed on unicornC.uc_open() with error returned: {} ({s})", .{ err, unicorn.uc_strerror(err) }); + return; + }; + + // map 2MB memory for this emulation + try unicorn.uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, unicornC.UC_PROT_ALL); + + // write machine code to be emulated to memory + try unicorn.uc_mem_write(uc, ADDRESS, RISCV_CODE, RISCV_CODE.len - 1); + + // initialize machine registers + try unicorn.uc_reg_write(uc, unicornC.UC_RISCV_REG_A0, @as(?*anyopaque, @ptrCast(@constCast(&a0)))); + try unicorn.uc_reg_write(uc, unicornC.UC_RISCV_REG_A1, @as(?*anyopaque, @ptrCast(@constCast(&a1)))); + + // tracing all basic blocks with customized callback + try unicorn.uc_hook_add(uc, &trace1, unicornC.UC_HOOK_BLOCK, @as(?*anyopaque, @ptrCast(@constCast(&hook_block))), null, 1, 0); + + // tracing all instruction + try unicorn.uc_hook_add(uc, &trace2, unicornC.UC_HOOK_CODE, @as(?*anyopaque, @ptrCast(@constCast(&hook_block))), null, 1, 0); + + // emulate 1 instruction + unicorn.uc_emu_start(uc, ADDRESS, ADDRESS + 4, 0, 0) catch |err| { + log.err("Failed on unicornC.uc_emu_start() with error returned: {}", .{err}); + }; + + try unicorn.uc_reg_read(uc, unicornC.UC_RISCV_REG_A0, @as(?*anyopaque, @ptrCast(@constCast(&a0)))); + try unicorn.uc_reg_read(uc, unicornC.UC_RISCV_REG_A1, @as(?*anyopaque, @ptrCast(@constCast(&a1)))); + + log.info(">>> A0 = 0x{}", .{a0}); + log.info(">>> A1 = 0x{}", .{a1}); + + // emulate one more instruction + unicorn.uc_emu_start(uc, ADDRESS + 4, ADDRESS + 8, 0, 0) catch |err| { + log.err("Failed on unicornC.uc_emu_start() with error returned: {}", .{err}); + }; + + // now print out some registers + log.info(">>> Emulation done. Below is the CPU context", .{}); + + try unicorn.uc_reg_read(uc, unicornC.UC_RISCV_REG_A0, @as(?*anyopaque, @ptrCast(@constCast(&a0)))); + try unicorn.uc_reg_read(uc, unicornC.UC_RISCV_REG_A1, @as(?*anyopaque, @ptrCast(@constCast(&a1)))); + + log.info(">>> A0 = 0x{}", .{a0}); + log.info(">>> A1 = 0x{}", .{a1}); + + try unicorn.uc_close(uc); +} diff --git a/bindings/zig/tools/zigcc.cmd b/bindings/zig/tools/zigcc.cmd new file mode 100644 index 00000000..a94add2e --- /dev/null +++ b/bindings/zig/tools/zigcc.cmd @@ -0,0 +1,3 @@ +@echo off + +zig cc -fno-sanitize=all %* \ No newline at end of file diff --git a/bindings/zig/tools/zigcc.sh b/bindings/zig/tools/zigcc.sh new file mode 100755 index 00000000..9c472266 --- /dev/null +++ b/bindings/zig/tools/zigcc.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash + +`which zig` cc -fno-sanitize=all $@ \ No newline at end of file diff --git a/bindings/zig/unicorn/unicorn.zig b/bindings/zig/unicorn/unicorn.zig index 43d19738..270d007d 100644 --- a/bindings/zig/unicorn/unicorn.zig +++ b/bindings/zig/unicorn/unicorn.zig @@ -1,3 +1,5 @@ +/// Public include's + // Architectures pub const arm = @import("arm_const.zig"); pub const arm64 = @import("arm64_const.zig"); @@ -9,5 +11,192 @@ pub const tricore = @import("tricore_const.zig"); pub const sparc = @import("sparc_const.zig"); pub const s390x = @import("s390x_const.zig"); pub const x86 = @import("x86_const.zig"); -// Unicorn -pub const unicorn = @import("unicorn_const.zig"); + +// Unicorn consts +pub usingnamespace @import("unicorn_const.zig"); +// C include +pub const c = @cImport(@cInclude("unicorn/unicorn.h")); + +pub fn uc_version(major: [*c]c_uint, minor: [*c]c_uint) c_uint { + return c.uc_version(major, minor); +} +pub fn uc_arch_supported(arch: c.uc_arch) bool { + return c.uc_arch_supported(arch); +} +pub fn uc_open(arch: c.uc_arch, mode: c.uc_mode, uc: [*c]?*c.uc_engine) !void { + try getErrors(c.uc_open(arch, mode, uc)); +} +pub fn uc_close(uc: ?*c.uc_engine) !void { + try getErrors(c.uc_close(uc)); +} +pub fn uc_query(uc: ?*c.uc_engine, @"type": c.uc_query_type, result: [*c]usize) !void { + try getErrors(c.uc_query(uc, @"type", result)); +} +pub fn uc_ctl(uc: ?*c.uc_engine, control: c.uc_control_type) !void { + try getErrors(c.uc_ctl(uc, control)); +} +pub fn uc_errno(uc: ?*c.uc_engine) !void { + try getErrors(c.uc_errno(uc)); +} +pub fn uc_strerror(code: Error) [*:0]const u8 { + return switch (code) { + error.ucErrNoMemory => c.uc_strerror(c.UC_ERR_NOMEM), + error.ucErrArch => c.uc_strerror(c.UC_ERR_ARCH), + error.ucErrHandle => c.uc_strerror(c.UC_ERR_HANDLE), + error.ucErrMode => c.uc_strerror(c.UC_ERR_MODE), + error.ucErrVersion => c.uc_strerror(c.UC_ERR_VERSION), + error.ucErrReadUnmapped => c.uc_strerror(c.UC_ERR_READ_UNMAPPED), + error.ucErrWriteUnmapped => c.uc_strerror(c.UC_ERR_WRITE_UNMAPPED), + error.ucErrFetchUnmapped => c.uc_strerror(c.UC_ERR_FETCH_UNMAPPED), + error.ucErrHook => c.uc_strerror(c.UC_ERR_HOOK), + error.ucErrInvalidInstruction => c.uc_strerror(c.UC_ERR_INSN_INVALID), + error.ucErrMap => c.uc_strerror(c.UC_ERR_MAP), + error.ucErrWriteProtected => c.uc_strerror(c.UC_ERR_WRITE_PROT), + error.ucErrReadProtected => c.uc_strerror(c.UC_ERR_READ_PROT), + error.ucErrFetchProtected => c.uc_strerror(c.UC_ERR_FETCH_PROT), + error.ucErrInvalidArgument => c.uc_strerror(c.UC_ERR_ARG), + error.ucErrReadUnaligned => c.uc_strerror(c.UC_ERR_READ_UNALIGNED), + error.ucErrWriteUnaligned => c.uc_strerror(c.UC_ERR_WRITE_UNALIGNED), + error.ucErrFetchUnaligned => c.uc_strerror(c.UC_ERR_FETCH_UNALIGNED), + error.ucErrHookAlreadyExists => c.uc_strerror(c.UC_ERR_HOOK_EXIST), + error.ucErrResource => c.uc_strerror(c.UC_ERR_RESOURCE), + error.ucErrException => c.uc_strerror(c.UC_ERR_EXCEPTION), + }; +} +pub fn uc_reg_write(uc: ?*c.uc_engine, regid: c_int, value: ?*const anyopaque) !void { + try getErrors(c.uc_reg_write(uc, regid, value)); +} +pub fn uc_reg_read(uc: ?*c.uc_engine, regid: c_int, value: ?*anyopaque) !void { + try getErrors(c.uc_reg_read(uc, regid, value)); +} +pub fn uc_reg_write_batch(uc: ?*c.uc_engine, regs: [*c]c_int, vals: [*c]const ?*anyopaque, count: c_int) !void { + try getErrors(c.uc_reg_write_batch(uc, regs, vals, count)); +} +pub fn uc_reg_read_batch(uc: ?*c.uc_engine, regs: [*c]c_int, vals: [*c]?*anyopaque, count: c_int) !void { + try getErrors(c.uc_reg_read_batch(uc, regs, vals, count)); +} +pub fn uc_mem_write(uc: ?*c.uc_engine, address: u64, bytes: ?*const anyopaque, size: usize) !void { + try getErrors(c.uc_mem_write(uc, address, bytes, size)); +} +pub fn uc_mem_read(uc: ?*c.uc_engine, address: u64, bytes: ?*anyopaque, size: usize) !void { + try getErrors(c.uc_mem_read(uc, address, bytes, size)); +} +pub fn uc_emu_start(uc: ?*c.uc_engine, begin: u64, until: u64, timeout: u64, count: usize) !void { + try getErrors(c.uc_emu_start(uc, begin, until, timeout, count)); +} +pub fn uc_emu_stop(uc: ?*c.uc_engine) !void { + try getErrors(c.uc_emu_stop(uc)); +} +pub fn uc_hook_add(uc: ?*c.uc_engine, hh: [*c]c.uc_hook, @"type": c_int, callback: ?*anyopaque, user_data: ?*anyopaque, begin: u64, end: u64) !void { + try getErrors(c.uc_hook_add(uc, hh, @"type", callback, user_data, begin, end)); +} +pub fn uc_hook_del(uc: ?*c.uc_engine, hh: c.uc_hook) !void { + try getErrors(c.uc_hook_del(uc, hh)); +} +pub fn uc_mem_map(uc: ?*c.uc_engine, address: u64, size: usize, perms: u32) !void { + try getErrors(c.uc_mem_map(uc, address, size, perms)); +} +pub fn uc_mem_map_ptr(uc: ?*c.uc_engine, address: u64, size: usize, perms: u32, ptr: ?*anyopaque) !void { + try getErrors(c.uc_mem_map_ptr(uc, address, size, perms, ptr)); +} +pub fn uc_mmio_map(uc: ?*c.uc_engine, address: u64, size: usize, read_cb: c.uc_cb_mmio_read_t, user_data_read: ?*anyopaque, write_cb: c.uc_cb_mmio_write_t, user_data_write: ?*anyopaque) !void { + try getErrors(c.uc_mmio_map(uc, address, size, read_cb, user_data_read, write_cb, user_data_write)); +} +pub fn uc_mem_unmap(uc: ?*c.uc_engine, address: u64, size: usize) !void { + try getErrors(c.uc_mem_unmap(uc, address, size)); +} +pub fn uc_mem_protect(uc: ?*c.uc_engine, address: u64, size: usize, perms: u32) !void { + try getErrors(c.uc_mem_protect(uc, address, size, perms)); +} +pub fn uc_mem_regions(uc: ?*c.uc_engine, regions: [*c][*c]c.uc_mem_region, count: [*c]u32) !void { + try getErrors(c.uc_mem_regions(uc, regions, count)); +} +pub fn uc_context_alloc(uc: ?*c.uc_engine, context: [*c]?*c.uc_context) !void { + try getErrors(c.uc_context_alloc(uc, context)); +} +pub fn uc_free(mem: ?*anyopaque) !void { + try getErrors(c.uc_free(mem)); +} +pub fn uc_context_save(uc: ?*c.uc_engine, context: ?*c.uc_context) !void { + try getErrors(c.uc_context_save(uc, context)); +} +pub fn uc_context_reg_write(ctx: ?*c.uc_context, regid: c_int, value: ?*const anyopaque) !void { + try getErrors(c.uc_context_reg_write(ctx, regid, value)); +} +pub fn uc_context_reg_read(ctx: ?*c.uc_context, regid: c_int, value: ?*anyopaque) !void { + try getErrors(c.uc_context_reg_read(ctx, regid, value)); +} +pub fn uc_context_reg_write_batch(ctx: ?*c.uc_context, regs: [*c]c_int, vals: [*c]const ?*anyopaque, count: c_int) !void { + try getErrors(c.uc_context_reg_write_batch(ctx, regs, vals, count)); +} +pub fn uc_context_reg_read_batch(ctx: ?*c.uc_context, regs: [*c]c_int, vals: [*c]?*anyopaque, count: c_int) !void { + try getErrors(c.uc_context_reg_read_batch(ctx, regs, vals, count)); +} +pub fn uc_context_restore(uc: ?*c.uc_engine, context: ?*c.uc_context) !void { + try getErrors(c.uc_context_restore(uc, context)); +} +pub fn uc_context_size(uc: ?*c.uc_engine) usize { + try getErrors(c.uc_context_size(uc)); +} +pub fn uc_context_free(context: ?*c.uc_context) !void { + try getErrors(c.uc_context_free(context)); +} + +pub const Error = error{ + ucErrNoMemory, + ucErrArch, + ucErrHandle, + ucErrMode, + ucErrVersion, + ucErrReadUnmapped, + ucErrWriteUnmapped, + ucErrFetchUnmapped, + ucErrHook, + ucErrInvalidInstruction, + ucErrMap, + ucErrWriteProtected, + ucErrReadProtected, + ucErrFetchProtected, + ucErrInvalidArgument, + ucErrReadUnaligned, + ucErrWriteUnaligned, + ucErrFetchUnaligned, + ucErrHookAlreadyExists, + ucErrResource, + ucErrException, +}; + +pub fn errorsToZig(err: c.uc_err) Error!c_int { + return switch (err) { + //c.UC_ERR_OK - isn't error + c.UC_ERR_NOMEM => error.ucErrNoMemory, + c.UC_ERR_ARCH => error.ucErrArch, + c.UC_ERR_HANDLE => error.ucErrHandle, + c.UC_ERR_MODE => error.ucErrMode, + c.UC_ERR_VERSION => error.ucErrVersion, + c.UC_ERR_READ_UNMAPPED => error.ucErrReadUnmapped, + c.UC_ERR_WRITE_UNMAPPED => error.ucErrWriteUnmapped, + c.UC_ERR_FETCH_UNMAPPED => error.ucErrFetchUnmapped, + c.UC_ERR_HOOK => error.ucErrHook, + c.UC_ERR_INSN_INVALID => error.ucErrInvalidInstruction, + c.UC_ERR_MAP => error.ucErrMap, + c.UC_ERR_WRITE_PROT => error.ucErrWriteProtected, + c.UC_ERR_READ_PROT => error.ucErrReadProtected, + c.UC_ERR_FETCH_PROT => error.ucErrFetchProtected, + c.UC_ERR_ARG => error.ucErrInvalidArgument, + c.UC_ERR_READ_UNALIGNED => error.ucErrReadUnaligned, + c.UC_ERR_WRITE_UNALIGNED => error.ucErrWriteUnaligned, + c.UC_ERR_FETCH_UNALIGNED => error.ucErrFetchUnaligned, + c.UC_ERR_HOOK_EXIST => error.ucErrHookAlreadyExists, + c.UC_ERR_RESOURCE => error.ucErrResource, + c.UC_ERR_EXCEPTION => error.ucErrException, + else => -1, + }; +} + +fn getErrors(err: c.uc_err) !void { + if (try errorsToZig(err) == c.UC_ERR_OK) + return; +} + +pub const log = @import("std").log.scoped(.unicorn); diff --git a/build.zig b/build.zig new file mode 100644 index 00000000..fbd9c740 --- /dev/null +++ b/build.zig @@ -0,0 +1,244 @@ +//! License: GNU GENERAL PUBLIC LICENSE Version 2 + +const std = @import("std"); + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) void { + if (comptime !checkVersion()) + @compileError("Please! Update zig toolchain to >= v0.11!"); + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + // flag: -DSamples=True/False + const samples = b.option(bool, "Samples", "Build all Samples [default: true]") orelse true; + + // Build Samples + + if (samples) { + // Zig + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .zig, + .filepath = "bindings/zig/sample/sample_riscv_zig.zig", + }); + + // C + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .c, + .filepath = "samples/sample_arm.c", + }); + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .c, + .filepath = "samples/sample_arm64.c", + }); + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .c, + .filepath = "samples/sample_ctl.c", + }); + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .c, + .filepath = "samples/sample_batch_reg.c", + }); + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .c, + .filepath = "samples/sample_m68k.c", + }); + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .c, + .filepath = "samples/sample_riscv.c", + }); + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .c, + .filepath = "samples/sample_sparc.c", + }); + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .c, + .filepath = "samples/sample_s390x.c", + }); + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .c, + .filepath = "samples/shellcode.c", + }); + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .c, + .filepath = "samples/sample_tricore.c", + }); + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .c, + .filepath = "samples/sample_x86.c", + }); + buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = .c, + .filepath = "samples/sample_x86_32_gdt_and_seg_regs.c", + }); + } + + // CMake Build + const cmake = cmakeBuild(b); + const cmake_step = b.step("cmake", "Run cmake build"); + cmake_step.dependOn(&cmake.step); +} + +fn buildExe(b: *std.Build, info: BuildInfo) void { + const execonfig: std.Build.ExecutableOptions = switch (info.filetype) { + .c, .cpp => .{ + .name = info.filename(), + .target = info.target, + .optimize = info.optimize, + }, + else => .{ + .name = info.filename(), + .target = info.target, + .optimize = info.optimize, + .root_source_file = .{ + .path = info.filepath, + }, + }, + }; + const unicornBuild = b.addExecutable(execonfig); + + if (info.filetype != .zig) + unicornBuild.addCSourceFile(.{ + .file = .{ .path = info.filepath }, + .flags = &.{ + "-Wall", + "-Werror", + "-fno-sanitize=all", + "-Wshadow", + }, + }) + else + unicornBuild.addAnonymousModule("unicorn", .{ + .source_file = .{ + .path = "bindings/zig/unicorn/unicorn.zig", + }, + }); + unicornBuild.addIncludePath(.{ .path = "include" }); + // need run cmake before + unicornBuild.addLibraryPath(.{ .path = "build" }); + if (info.target.isWindows()) { + unicornBuild.want_lto = false; + unicornBuild.linkSystemLibraryName("unicorn.dll"); + } else unicornBuild.linkSystemLibrary("unicorn"); + + // linking to OS-LibC or static-linking for: + // Musl(Linux) [e.g: -Dtarget=native-linux-musl] + // MinGW(Windows) [e.g: -Dtarget=native-windows-gnu (default)] + if (info.filetype == .cpp and info.target.getAbi() != .msvc) + unicornBuild.linkLibCpp() // static-linking LLVM-libcxx (all targets) + libC + else + unicornBuild.linkLibC(); + + // This declares intent for the executable to be installed into the + // standard location when the user invokes the "install" step (the default + // step when running `zig build`). + b.installArtifact(unicornBuild); + + // This *creates* a RunStep in the build graph, to be executed when another + // step is evaluated that depends on it. The next line below will establish + // such a dependency. + const run_cmd = b.addRunArtifact(unicornBuild); + + // By making the run step depend on the install step, it will be run from the + // installation directory rather than directly from within the cache directory. + // This is not necessary, however, if the application depends on other installed + // files, this ensures they will be present and in the expected location. + run_cmd.step.dependOn(b.getInstallStep()); + + // This allows the user to pass arguments to the application in the build + // command itself, like this: `zig build run -- arg1 arg2 etc` + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build run` + // This will evaluate the `run` step rather than the default, which is "install". + const run_step = b.step(info.filename(), b.fmt("Run the {s}.", .{info.filename()})); + run_step.dependOn(&run_cmd.step); +} + +fn cmakeBuild(b: *std.Build) *std.Build.Step.Run { + const preconf = b.addSystemCommand(&.{ + "cmake", + "-B", + "build", + "-DZIG_BUILD=ON", + "-DUNICORN_BUILD_TESTS=OFF", + "-DUNICORN_INSTALL=OFF", + "-DCMAKE_BUILD_TYPE=Release", + }); + const cmakebuild = b.addSystemCommand(&.{ + "cmake", + "--build", + "build", + "--config", + "release", + "--parallel", + }); + cmakebuild.step.dependOn(&preconf.step); + return cmakebuild; +} + +fn checkVersion() bool { + const builtin = @import("builtin"); + if (!@hasDecl(builtin, "zig_version")) { + return false; + } + + const needed_version = std.SemanticVersion.parse("0.11.0") catch unreachable; + const version = builtin.zig_version; + const order = version.order(needed_version); + return order != .lt; +} + +const BuildInfo = struct { + filepath: []const u8, + filetype: enum { + c, + cpp, + zig, + }, + target: std.zig.CrossTarget, + optimize: std.builtin.OptimizeMode, + + fn filename(self: BuildInfo) []const u8 { + var split = std.mem.splitSequence(u8, std.fs.path.basename(self.filepath), "."); + return split.first(); + } +}; diff --git a/bundle_static.cmake b/cmake/bundle_static.cmake similarity index 100% rename from bundle_static.cmake rename to cmake/bundle_static.cmake diff --git a/mingw-w64.cmake b/cmake/mingw-w64.cmake similarity index 100% rename from mingw-w64.cmake rename to cmake/mingw-w64.cmake diff --git a/cmake/zig.cmake b/cmake/zig.cmake new file mode 100644 index 00000000..7f010255 --- /dev/null +++ b/cmake/zig.cmake @@ -0,0 +1,9 @@ +set(CMAKE_CROSSCOMPILING TRUE) +# set the compiler +if(WIN32) + SET(ZIG_CC ${CMAKE_SOURCE_DIR}/bindings/zig/tools/zigcc.cmd) +else() + SET(ZIG_CC ${CMAKE_SOURCE_DIR}/bindings/zig/tools/zigcc.sh) +endif() +SET(CMAKE_C_COMPILER_ID ${ZIG_CC}) +SET(CMAKE_C_COMPILER ${ZIG_CC})