diff --git a/.github/workflows/zigbuild.yml b/.github/workflows/zigbuild.yml index 8be1b65d..1515cfd9 100644 --- a/.github/workflows/zigbuild.yml +++ b/.github/workflows/zigbuild.yml @@ -15,11 +15,11 @@ on: pull_request: jobs: - build: + build-ubuntu: strategy: fail-fast: false matrix: - runs-on: [ubuntu-latest, macos-latest] + runs-on: [ubuntu-latest] runs-on: ${{ matrix.runs-on }} steps: - uses: actions/checkout@v3 @@ -28,7 +28,7 @@ jobs: fetch-depth: 0 - uses: goto-bus-stop/setup-zig@v2 with: - version: 0.11.0 + version: 0.12.0 - uses: lukka/get-cmake@latest with: cmakeVersion: latest @@ -39,7 +39,33 @@ jobs: - name: Build Summary run: zig build --summary all -freference-trace + build-macos: + strategy: + fail-fast: false + matrix: + runs-on: [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.12.0 + - uses: lukka/get-cmake@latest + with: + cmakeVersion: latest + ninjaVersion: latest + # MacOS has a hard process limit that we will hit if we do a + # parallel build, so disable parallel cmake build (manual + # option set in `build.zig`) + - name: CMake Build + run: zig build -Dparallel=false cmake + + - name: Build Summary + run: zig build --summary all -freference-trace # =================================================================== # zig-mingw: # runs-on: windows-latest @@ -78,4 +104,4 @@ jobs: # else # zig build --summary all -freference-trace -Dtarget=${{ matrix.arch }}-windows # fi - \ No newline at end of file + diff --git a/bindings/zig/README.md b/bindings/zig/README.md index 47bdda0e..bf40509f 100644 --- a/bindings/zig/README.md +++ b/bindings/zig/README.md @@ -7,4 +7,18 @@ based on [QEMU](http://www.qemu.org/). ## How to use -Add to your project the file `unicorn/unicorn.zig` that will manage all the available architectures. \ No newline at end of file +Using the [Zig Build System](https://ziglang.org/learn/build-system/), you can include +the following into your local `build.zig.zon` + +``` zig +.{ + .dependencies = .{ + .unicorn = .{ + .url = "https://github.com/unicorn-engine/unicorn/archive/.tar.gz", + .hash = "", + } + }, +} +``` + +Note that currently the only module exported publicly is `unicorn-sys` diff --git a/bindings/zig/sample/sample_riscv_zig.zig b/bindings/zig/sample/sample_riscv_zig.zig index ee372ade..6012d81f 100644 --- a/bindings/zig/sample/sample_riscv_zig.zig +++ b/bindings/zig/sample/sample_riscv_zig.zig @@ -38,8 +38,8 @@ fn hook_code3(uc: ?*unicornC.uc_engine, address: u64, size: u32, user_data: ?*an 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; + const algined_address = address & 0xFFFFFFFFFFFFF000; + const 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 }); diff --git a/build.zig b/build.zig index fbd9c740..8c6e8caa 100644 --- a/build.zig +++ b/build.zig @@ -1,13 +1,55 @@ //! License: GNU GENERAL PUBLIC LICENSE Version 2 const std = @import("std"); +const MIN_ZIG_VERSION: []const u8 = "0.12.0"; +const MIN_ZIG_VERSION_ERR_MSG = "Please! Update zig toolchain to >= v" ++ MIN_ZIG_VERSION; + +const SampleFileTypes = enum { + c, + cpp, + zig, +}; + +const SampleDescripton = struct { + file_type: SampleFileTypes, + root_file_path: []const u8, +}; + +/// Create a module for the Zig Bindings +/// +/// This will also get exported as a library that other zig projects can use +/// as a dependency via the zig build system. +fn create_unicorn_sys(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Module { + const unicorn_sys = b.addModule("unicorn-sys", .{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("bindings/zig/unicorn/unicorn.zig"), + }); + + // link libc + unicorn_sys.link_libc = true; + + // we need the c header for the zig-bindings + unicorn_sys.addIncludePath(b.path("include")); + unicorn_sys.addLibraryPath(b.path("build")); + + // Linking to the Unicorn library + if (target.result.abi == .msvc and target.result.os.tag == .windows) { + unicorn_sys.linkSystemLibrary("unicorn.dll", .{}); + } else { + unicorn_sys.linkSystemLibrary("unicorn", .{}); + } + + return unicorn_sys; +} // 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!"); + @compileError(MIN_ZIG_VERSION_ERR_MSG); + // 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 @@ -19,102 +61,60 @@ pub fn build(b: *std.Build) void { // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); + // Give the user the options to perform the cmake build in parallel or not + // (eg. ci on macos will fail if parallel is enabled) + // + // flag: -Dparallel=true/false + const parallel_cmake = b.option(bool, "parallel", "Enable parallel cmake build") orelse true; + // flag: -DSamples=True/False const samples = b.option(bool, "Samples", "Build all Samples [default: true]") orelse true; + const sample_bins = [_]SampleDescripton{ + .{ .file_type = .zig, .root_file_path = "bindings/zig/sample/sample_riscv_zig.zig" }, + .{ .file_type = .c, .root_file_path = "samples/sample_arm.c" }, + .{ .file_type = .c, .root_file_path = "samples/sample_arm64.c" }, + .{ .file_type = .c, .root_file_path = "samples/sample_ctl.c" }, + .{ .file_type = .c, .root_file_path = "samples/sample_batch_reg.c" }, + .{ .file_type = .c, .root_file_path = "samples/sample_m68k.c" }, + .{ .file_type = .c, .root_file_path = "samples/sample_riscv.c" }, + .{ .file_type = .c, .root_file_path = "samples/sample_sparc.c" }, + .{ .file_type = .c, .root_file_path = "samples/sample_s390x.c" }, + .{ .file_type = .c, .root_file_path = "samples/shellcode.c" }, + .{ .file_type = .c, .root_file_path = "samples/sample_tricore.c" }, + .{ .file_type = .c, .root_file_path = "samples/sample_x86.c" }, + .{ .file_type = .c, .root_file_path = "samples/sample_x86_32_gdt_and_seg_regs.c" }, + }; + + // make a module for Zig Bindings + const unicorn_sys = create_unicorn_sys(b, target, optimize); + // Build Samples - if (samples) { - // Zig - buildExe(b, .{ - .target = target, - .optimize = optimize, - .filetype = .zig, - .filepath = "bindings/zig/sample/sample_riscv_zig.zig", - }); + for (sample_bins) |sample| { + const sample_bin = buildExe(b, .{ + .target = target, + .optimize = optimize, + .filetype = sample.file_type, + .filepath = sample.root_file_path, + }); - // 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", - }); + // import the unicorn sys module if this is a zig build + if (sample.file_type == .zig) { + sample_bin.root_module.addImport("unicorn", unicorn_sys); + } + } } // CMake Build - const cmake = cmakeBuild(b); + const cmake = cmakeBuild(b, parallel_cmake); const cmake_step = b.step("cmake", "Run cmake build"); cmake_step.dependOn(&cmake.step); } -fn buildExe(b: *std.Build, info: BuildInfo) void { +fn buildExe(b: *std.Build, info: BuildInfo) *std.Build.Step.Compile { + const target = info.stdTarget(); + const execonfig: std.Build.ExecutableOptions = switch (info.filetype) { .c, .cpp => .{ .name = info.filename(), @@ -130,10 +130,10 @@ fn buildExe(b: *std.Build, info: BuildInfo) void { }, }, }; - const unicornBuild = b.addExecutable(execonfig); + const exe = b.addExecutable(execonfig); - if (info.filetype != .zig) - unicornBuild.addCSourceFile(.{ + if (info.filetype != .zig) { + exe.addCSourceFile(.{ .file = .{ .path = info.filepath }, .flags = &.{ "-Wall", @@ -141,38 +141,42 @@ fn buildExe(b: *std.Build, info: BuildInfo) void { "-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(); + // Ensure the C headers are available + exe.addIncludePath(.{ .path = "include" }); + + // Ensure the C library is available + exe.addLibraryPath(.{ .path = "build" }); + + // 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 target.abi != .msvc) + exe.linkLibCpp() // static-linking LLVM-libcxx (all targets) + libC + else + exe.linkLibC(); + + // Now link the C library + if (target.abi == .msvc and target.os.tag == .windows) { + exe.linkSystemLibrary("unicorn.dll"); + } else exe.linkSystemLibrary("unicorn"); + } + + // Linking to the Unicorn library + if (target.abi == .msvc and target.os.tag == .windows) { + exe.want_lto = false; + } // 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); + b.installArtifact(exe); // 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); + const run_cmd = b.addRunArtifact(exe); // 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. @@ -191,9 +195,27 @@ fn buildExe(b: *std.Build, info: BuildInfo) void { // 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); + + return exe; } -fn cmakeBuild(b: *std.Build) *std.Build.Step.Run { +const PARALLEL_CMAKE_COMMAND = [_][]const u8{ + "cmake", + "--build", + "build", + "--config", + "release", + "--parallel", +}; + +const SINGLE_CMAKE_COMMAND = [_][]const u8{ + "cmake", + "--build", + "build", + "--config", + "release", +}; +fn cmakeBuild(b: *std.Build, parallel_cmake: bool) *std.Build.Step.Run { const preconf = b.addSystemCommand(&.{ "cmake", "-B", @@ -203,25 +225,27 @@ fn cmakeBuild(b: *std.Build) *std.Build.Step.Run { "-DUNICORN_INSTALL=OFF", "-DCMAKE_BUILD_TYPE=Release", }); - const cmakebuild = b.addSystemCommand(&.{ - "cmake", - "--build", - "build", - "--config", - "release", - "--parallel", + + // build in parallel if requested + const cmakebuild = b.addSystemCommand(blk: { + if (parallel_cmake) { + break :blk &PARALLEL_CMAKE_COMMAND; + } else { + break :blk &SINGLE_CMAKE_COMMAND; + } }); cmakebuild.step.dependOn(&preconf.step); return cmakebuild; } +// ensures the currently in-use zig version is at least the minimum required 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 needed_version = std.SemanticVersion.parse(MIN_ZIG_VERSION) catch unreachable; const version = builtin.zig_version; const order = version.order(needed_version); return order != .lt; @@ -229,16 +253,16 @@ fn checkVersion() bool { const BuildInfo = struct { filepath: []const u8, - filetype: enum { - c, - cpp, - zig, - }, - target: std.zig.CrossTarget, + filetype: SampleFileTypes, + target: std.Build.ResolvedTarget, 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(); } + + fn stdTarget(self: *const BuildInfo) std.Target { + return self.target.result; + } }; diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 00000000..4cdd174d --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,5 @@ +.{ + .name = "unicorn", + .version = "2.0.0", + .paths = .{""}, +}