diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d3da778..52b0005 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,8 +12,10 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: goto-bus-stop/setup-zig@v2 + - uses: actions/checkout@v3 + - uses: mlugg/setup-zig@v2 + with: + version: 0.14.1 - run: zig fmt --check *.zig test: @@ -23,6 +25,8 @@ jobs: os: [ubuntu-latest, macos-latest] runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v2 - - uses: goto-bus-stop/setup-zig@v2 + - uses: actions/checkout@v3 + - uses: mlugg/setup-zig@v2 + with: + version: 0.14.1 - run: zig build test diff --git a/build.zig b/build.zig index 46a1594..64e1471 100644 --- a/build.zig +++ b/build.zig @@ -12,11 +12,12 @@ pub fn build(b: *std.Build) void { compiled_capstone.getEmittedIncludeTree().addStepDependencies(&compiled_capstone.step); const capstone_c = b.addTranslateC(.{ - .root_source_file = compiled_capstone.getEmittedIncludeTree().path(b, "capstone/capstone.h"), + .root_source_file = compiled_capstone.getEmittedIncludeTree().path(b, "capstone.h"), .target = target, .optimize = optimize, .link_libc = true, }); + const capstone_c_mod = capstone_c.createModule(); const mod = b.addModule("capstone-bindings-zig", .{ .root_source_file = b.path("capstone.zig"), @@ -25,7 +26,7 @@ pub fn build(b: *std.Build) void { .imports = &.{ .{ .name = "capstone-c", - .module = capstone_c.createModule(), + .module = capstone_c_mod, }, }, }); @@ -34,13 +35,16 @@ pub fn build(b: *std.Build) void { mod.addIncludePath(compiled_capstone.getEmittedIncludeTree()); const mod_test = b.addTest(.{ - .root_source_file = mod.root_source_file.?, + .root_source_file = b.path("src/tests.zig"), .target = target, .optimize = optimize, }); mod_test.step.dependOn(&compiled_capstone.step); - mod_test.root_module.addImport("capstone-c", capstone_c.createModule()); + mod_test.root_module.addImport("capstone-c", capstone_c_mod); + mod_test.addLibraryPath(compiled_capstone.getEmittedBin().dirname()); + mod_test.linkLibrary(capstone.artifact("capstone")); + mod_test.addIncludePath(compiled_capstone.getEmittedIncludeTree()); const run_lib_tests = b.addRunArtifact(mod_test); const test_step = b.step("test", "Run the library tests"); diff --git a/build.zig.zon b/build.zig.zon index c7337cb..027cfbd 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,12 +1,13 @@ .{ - .name = "capstone-bindings-zig", + .name = .capstone_bindings_zig, .version = "5.0.1", + .fingerprint = 0x52b01062163ea867, .minimum_zig_version = "0.14.0", .dependencies = .{ .capstone = .{ - .url = "git+https://github.com/allyourcodebase/capstone.git#5.0.1-1", - .hash = "1220f284ea55271b5b68edb4471d10c622e5d5c220dd342c6e96ff50d3b801f46486", + .url = "git+https://github.com/allyourcodebase/capstone.git#5.0.1-2", + .hash = "capstone-5.0.1-tmNUgTRHAAC1nFVy0colCWwUSRm05SWdD-4kU5MK4ZpH", }, }, diff --git a/capstone.zig b/capstone.zig index ee41a9b..b6c1ec1 100644 --- a/capstone.zig +++ b/capstone.zig @@ -1,6 +1,10 @@ const impl = @import("src/impl.zig"); -pub const Iter = impl.Iter; +pub const IterUnmanaged = impl.IterUnmanaged; +pub const IterManaged = impl.IterManaged; pub const Handle = impl.Handle; +pub const Regs = impl.Regs; +pub const Detail = impl.insn.Detail; +pub const Insn = impl.insn.Insn; pub const version = impl.version; pub const support = impl.support; pub const open = impl.open; @@ -11,6 +15,9 @@ pub const strerror = impl.strerror; pub const disasm = impl.disasm; pub const free = impl.free; pub const malloc = impl.malloc; +pub const createInsn = impl.createInsn; +pub const dupeInsn = impl.dupeInsn; +pub const destroyInsn = impl.destroyInsn; pub const disasmIterManaged = impl.disasmIterManaged; pub const disasmIter = impl.disasmIter; pub const regName = impl.regName; @@ -42,3 +49,27 @@ const enums = @import("src/enums.zig"); pub const Arch = enums.Arch; pub const Mode = enums.Mode; pub const Type = enums.Type; + +pub const ManagedHandle = @import("src/ManagedHandle.zig"); + +const arch = @import("src/arch/arch.zig"); +pub const x86 = arch.x86; +pub const arm64 = arch.arm64; +pub const arm = arch.arm; +pub const m68k = arch.m68k; +pub const mips = arch.mips; +pub const ppc = arch.ppc; +pub const sparc = arch.sparc; +pub const sysz = arch.sysz; +pub const xcore = arch.xcore; +pub const tms320c64x = arch.tms320c64x; +pub const m680x = arch.m680x; +pub const evm = arch.evm; +pub const mos65xx = arch.mos65xx; +pub const wasm = arch.wasm; +pub const bpf = arch.bpf; +pub const riscv = arch.riscv; +pub const sh = arch.sh; +pub const tricore = arch.tricore; + +pub const c = @import("capstone-c"); diff --git a/src/ManagedHandle.zig b/src/ManagedHandle.zig new file mode 100644 index 0000000..62f3017 --- /dev/null +++ b/src/ManagedHandle.zig @@ -0,0 +1,222 @@ +const builtin = @import("builtin"); +const std = @import("std"); + +const cs = @import("capstone-c"); + +const err = @import("error.zig"); +const insn = @import("insn.zig"); +const enums = @import("enums.zig"); +const impl = @import("impl.zig"); + +const iter = @import("iter.zig"); + +const Allocator = std.mem.Allocator; + +// To avoid heap allocations... +const tmp_storage = if (builtin.single_threaded) + struct { + var detail: insn.Detail = std.mem.zeroes(insn.Detail); + var ins: insn.Insn = std.mem.zeroes(insn.Insn); + fn getIns() *insn.Insn { + ins.detail = &detail; + return &ins; + } + } +else + struct { + threadlocal var detail: insn.Detail = std.mem.zeroes(insn.Detail); + threadlocal var ins: insn.Insn = std.mem.zeroes(insn.Insn); + fn getIns() *insn.Insn { + ins.detail = &detail; + return &ins; + } + }; + +const Self = @This(); + +pub const Options = struct { + /// Assembly output syntax + syntax: enums.Syntax = .DEFAULT, + /// Break down instruction structure into details + detail: bool = false, + /// Skip data when disassembling. Then engine is in SKIPDATA mode. + skipdata: bool = false, + /// Setup user-defined function for SKIPDATA option + /// The pointers must be valid till the handle is closed. + skipdata_setup: ?SkipDataOption = null, + /// Customize instruction mnemonic + /// Must be valid till the handle is closed if set. + mnemonic: []const MnemonicOption = &.{}, + /// print immediate operands in unsigned form + unsigned: bool = false, + /// ARM, prints branch immediates without offset. + no_branch_offset: bool = false, + + // cs_opt_mnem + pub const MnemonicOption = extern struct { + id: c_uint, + mnemonic: [*:0]const u8, + }; + // cs_skipdata + pub const SkipDataOption = extern struct { + mnemonic: [*:0]const u8, + callback: ?*const fn ([*c]const u8, usize, usize, ?*anyopaque) callconv(.c) usize, + user_data: ?*anyopaque = null, + }; +}; + +native: impl.Handle, +detail_on: bool, + +pub fn init(arch: enums.Arch, mode: enums.Mode, options: Options) !Self { + const handle = try impl.open(arch, mode); + + if (options.syntax != .DEFAULT) { + try impl.option(handle, .SYNTAX, @intFromEnum(options.syntax)); + } + + if (options.detail) { + try impl.option(handle, .DETAIL, cs.CS_OPT_ON); + } + + if (options.skipdata) { + try impl.option(handle, .SKIPDATA, cs.CS_OPT_ON); + } + + if (options.skipdata_setup) |setup| { + // cs_option copies the values. + try impl.option(handle, .SKIPDATA_SETUP, @intFromPtr(&setup)); + } + + if (options.unsigned) { + try impl.option(handle, .UNSIGNED, cs.CS_OPT_ON); + } + + if (options.no_branch_offset) { + try impl.option(handle, .NO_BRANCH_OFFSET, cs.CS_OPT_ON); + } + + if (options.mnemonic.len > 0) { + try impl.option(handle, .MNEMONIC, @intFromPtr(options.mnemonic.ptr)); + } + + return Self{ .native = handle, .detail_on = options.detail }; +} + +/// Allocates using the cs_malloc function or the user defined one which is set through cs_option/setup.initCapstone(Manually). +/// Should be freed using cs_free, capstone provided free function or the user defined one if set through option. +pub fn disasm(self: Self, code: []const u8, address: usize, count: usize) err.CapstoneError![]insn.Insn { + return impl.disasm(self.native, code, address, count); +} + +/// Copies using zig user-land allocator and frees the cs_malloc'd memory. +pub fn disasmAlloc(self: Self, allocator: std.mem.Allocator, code: []const u8, address: usize, count: usize) ![]insn.Insn { + const disass = try impl.disasm(self.native, code, address, count); + defer impl.free(disass); + return allocator.dupe(insn.Insn, disass); +} + +/// Uses global/threadlocal storage to avoid heap allocations for the single Insn struct. +pub fn disasmIter(self: Self, code: []const u8, address: u64) iter.IterUnmanaged { + return impl.disasmIter(self.native, code, address, tmp_storage.getIns()); +} + +/// Same as `impl.createInsn` but has more context. +pub fn createInsn(self: Self, allocator: Allocator) !*insn.Insn { + return impl.createInsn(allocator, self.detail_on); +} + +/// Creates a duplicate of the provided Insn object using the provided allocator. +/// The owner takes responsibility of the pointer. +/// Must be destroyed with `destroyInsn`. +pub fn dupeInsn(self: Self, allocator: Allocator, ins: *const insn.Insn) !*insn.Insn { + var new_ins = try self.createInsn(allocator); + const new_detail = new_ins.detail; + new_ins.* = ins.*; + + if (self.detail_on and ins.detail == null) { + std.io.getStdErr().writer().print("Cannot duplicate `ins`, doesn't have detail.\n", .{}) catch {}; + return error.NotEnoughFields; + } + + if (new_detail != null) { + new_detail.?.* = ins.detail.?.*; + } + new_ins.detail = new_detail; + + return new_ins; +} + +/// Same as impl.destroyInsn. +pub fn destroyInsn(_: Self, allocator: Allocator, ins: *insn.Insn) void { + impl.destroyInsn(allocator, ins); +} + +pub fn deinit(self: *Self) void { + impl.close(&self.native) catch |e| { + std.io.getStdErr().writer().print("Failed to close handle: {any}\n", .{impl.strerror(e)}) catch {}; + }; +} + +test "with options" { + const code = "\x75\x14"; + + var handle = try init(.X86, .@"64", .{ + .syntax = .INTEL, + .detail = true, + .mnemonic = &.{.{ .id = cs.X86_INS_JNE, .mnemonic = "jnz" }}, + }); + defer handle.deinit(); + + const disass = try handle.disasm(code, 0x1000, 0); + defer impl.free(disass); + + try std.testing.expectEqual(1, disass.len); + try std.testing.expect(disass[0].detail != null); + try std.testing.expectEqualStrings("jnz", disass[0].mnemonic[0..3]); +} + +test "disasm iter" { + const code = "\x75\x14"; + + var handle = try init(.X86, .@"64", .{ + .syntax = .INTEL, + .detail = true, + .mnemonic = &.{.{ .id = cs.X86_INS_JNE, .mnemonic = "jnz" }}, + }); + defer handle.deinit(); + + var dis_iter = handle.disasmIter(code, 0x1000); + const ins = dis_iter.next().?; + + try std.testing.expect(ins.detail != null); + try std.testing.expectEqualStrings("jnz", ins.mnemonic[0..3]); +} + +test "create, dupe and destroy Insn" { + const testing = @import("std").testing; + const allocator = testing.allocator; + + var handle = try init(.X86, .@"64", .{ + .syntax = .INTEL, + .detail = true, + }); + defer handle.deinit(); + + const ins = try handle.createInsn(allocator); + defer handle.destroyInsn(allocator, ins); + + ins.id = 0x1234; + ins.address = 0x1000; + ins.size = 2; + ins.detail.?.groups_count = 2; + + const duped_ins = try handle.dupeInsn(allocator, ins); + defer handle.destroyInsn(allocator, duped_ins); + + try testing.expectEqual(ins.id, duped_ins.id); + try testing.expectEqual(ins.address, duped_ins.address); + try testing.expectEqual(ins.size, duped_ins.size); + try testing.expectEqual(ins.detail.?.groups_count, duped_ins.detail.?.groups_count); + try testing.expect(ins.detail != duped_ins.detail); +} diff --git a/src/arch/arch.zig b/src/arch/arch.zig index 7e6c2a1..09a4280 100644 --- a/src/arch/arch.zig +++ b/src/arch/arch.zig @@ -1,21 +1,21 @@ -const x86 = @import("x86/all.zig"); -const arm64 = @import("arm64/all.zig"); -const arm = @import("arm/all.zig"); -const m68k = @import("m68k/all.zig"); -const mips = @import("mips/all.zig"); -const ppc = @import("ppc/all.zig"); -const sparc = @import("sparc/all.zig"); -const sysz = @import("sysz/all.zig"); -const xcore = @import("xcore/all.zig"); -const tms320c64x = @import("tms320c64x/all.zig"); -const m680x = @import("m680x/all.zig"); -const evm = @import("evm/all.zig"); -const mos65xx = @import("mos65xx/all.zig"); -const wasm = @import("wasm/all.zig"); -const bpf = @import("bpf/all.zig"); -const riscv = @import("riscv/all.zig"); -const sh = @import("sh/all.zig"); -const tricore = @import("tricore/all.zig"); +pub const x86 = @import("x86/all.zig"); +pub const arm64 = @import("arm64/all.zig"); +pub const arm = @import("arm/all.zig"); +pub const m68k = @import("m68k/all.zig"); +pub const mips = @import("mips/all.zig"); +pub const ppc = @import("ppc/all.zig"); +pub const sparc = @import("sparc/all.zig"); +pub const sysz = @import("sysz/all.zig"); +pub const xcore = @import("xcore/all.zig"); +pub const tms320c64x = @import("tms320c64x/all.zig"); +pub const m680x = @import("m680x/all.zig"); +pub const evm = @import("evm/all.zig"); +pub const mos65xx = @import("mos65xx/all.zig"); +pub const wasm = @import("wasm/all.zig"); +pub const bpf = @import("bpf/all.zig"); +pub const riscv = @import("riscv/all.zig"); +pub const sh = @import("sh/all.zig"); +pub const tricore = @import("tricore/all.zig"); pub const Arch = extern union { x86: x86.Arch, diff --git a/src/arch/arm/all.zig b/src/arch/arm/all.zig index 35b9167..3bca248 100644 --- a/src/arch/arm/all.zig +++ b/src/arch/arm/all.zig @@ -1,8 +1,15 @@ -const VectorData = @import("vectordata.zig").VectorData; -const Cps = @import("cps.zig"); -const Cc = @import("cc.zig").Cc; -const Barrier = @import("barrier.zig").Barrier; -const Operand = @import("operand.zig").Operand; +pub const Barrier = @import("barrier.zig").Barrier; +pub const Cc = @import("cc.zig").Cc; +pub const Cps = @import("cps.zig"); +pub const OpType = @import("op_type.zig").OpType; +pub const Register = @import("register.zig").Register; +pub const Setend = @import("setend.zig").Setend; +pub const Shift = @import("shift.zig").Shift; +pub const VectorData = @import("vectordata.zig").VectorData; +pub const OpMem = @import("op_mem.zig").OpMem; +pub const Shifter = @import("shifter.zig").Shifter; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { usermode: bool, diff --git a/src/arch/arm64/all.zig b/src/arch/arm64/all.zig index 6157f9a..137f5e1 100644 --- a/src/arch/arm64/all.zig +++ b/src/arch/arm64/all.zig @@ -1,4 +1,19 @@ +pub const Barrier = @import("barrier.zig").Barrier; pub const Cc = @import("cc.zig").Cc; +pub const Extender = @import("extender.zig").Extender; +pub const OpType = @import("op_type.zig").OpType; +pub const Prefetch = @import("prefetch.zig").Prefetch; +pub const Pstate = @import("pstate.zig").Pstate; +pub const Register = @import("register.zig").Register; +pub const Shift = @import("shift.zig").Shift; +pub const Svcr = @import("svcr.zig").Svcr; +pub const SysOp = @import("sys_op.zig").SysOp; +pub const Vas = @import("vas.zig").Vas; +pub const Sme = @import("sme.zig").Sme; +pub const Shifter = @import("shifter.zig").Shifter; +const instruction = @import("instruction.zig"); +pub const OpMem = instruction.OpMem; +pub const Instruction = instruction.Instruction; pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { diff --git a/src/arch/arm64/sys.zig b/src/arch/arm64/sys.zig deleted file mode 100644 index e69de29..0000000 diff --git a/src/arch/bpf/all.zig b/src/arch/bpf/all.zig index 14ac383..2437d5d 100644 --- a/src/arch/bpf/all.zig +++ b/src/arch/bpf/all.zig @@ -1,4 +1,8 @@ -const Operand = @import("operand.zig").Operand; +pub const OpMem = @import("op_mem.zig").OpMem; +pub const OpType = @import("op_type.zig").OpType; +pub const Register = @import("register.zig").Register; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { op_count: u8, diff --git a/src/arch/m680x/all.zig b/src/arch/m680x/all.zig index 4018330..a706532 100644 --- a/src/arch/m680x/all.zig +++ b/src/arch/m680x/all.zig @@ -1,4 +1,10 @@ -const Operand = @import("operand.zig").Operand; +pub const OpExt = @import("op_ext.zig").OpExt; +pub const OpRel = @import("op_rel.zig").OpRel; +pub const OpType = @import("op_type.zig").OpType; +pub const Register = @import("register.zig").Register; +pub const OpIdx = @import("op_idx.zig").OpIdx; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { flags: u8, diff --git a/src/arch/m68k/addr_mode.zig b/src/arch/m68k/address_mode.zig similarity index 100% rename from src/arch/m68k/addr_mode.zig rename to src/arch/m68k/address_mode.zig diff --git a/src/arch/m68k/all.zig b/src/arch/m68k/all.zig index 37cac9a..8a6933e 100644 --- a/src/arch/m68k/all.zig +++ b/src/arch/m68k/all.zig @@ -1,5 +1,15 @@ -const Operand = @import("operand.zig").Operand; -const OpSize = @import("op_size.zig").OpSize; +pub const AddressMode = @import("address_mode.zig").AddressMode; +pub const OpMem = @import("op_mem.zig").OpMem; +const opsize = @import("op_size.zig"); +pub const CpuSize = opsize.CpuSize; +pub const FpuSize = opsize.FpuSize; +pub const UnitSize = opsize.UnitSize; +pub const SizeType = opsize.SizeType; +pub const OpSize = opsize.OpSize; +pub const OpType = @import("op_type.zig").OpType; +pub const Register = @import("register.zig").Register; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { operands: [4]Operand, diff --git a/src/arch/m68k/op_size.zig b/src/arch/m68k/op_size.zig index db2cbb2..4635eb3 100644 --- a/src/arch/m68k/op_size.zig +++ b/src/arch/m68k/op_size.zig @@ -1,25 +1,25 @@ const cs = @import("capstone-c"); -const CpuSize = enum(cs.m68k_cpu_size) { +pub const CpuSize = enum(cs.m68k_cpu_size) { NONE, BYTE, WORD, LONG = 4, }; -const FpuSize = enum(cs.m68k_fpu_size) { +pub const FpuSize = enum(cs.m68k_fpu_size) { NONE, SINGLE = 4, DOUBLE = 8, EXTENDED = 12, }; -const UnitSize = extern union { +pub const UnitSize = extern union { cpu_size: CpuSize, fpu_size: FpuSize, }; -const SizeType = enum(cs.m68k_size_type) { +pub const SizeType = enum(cs.m68k_size_type) { INVALID, CPU, FPU, diff --git a/src/arch/m68k/operand.zig b/src/arch/m68k/operand.zig index 2eccfe4..b723ef1 100644 --- a/src/arch/m68k/operand.zig +++ b/src/arch/m68k/operand.zig @@ -1,7 +1,7 @@ const Instruction = @import("instruction.zig").Instruction; const OpMem = @import("op_mem.zig").OpMem; const OpType = @import("op_type.zig").OpType; -const AddressMode = @import("addr_mode.zig").AddressMode; +const AddressMode = @import("address_mode.zig").AddressMode; const BrDisp = extern struct { disp: i32, disp_size: u8 }; pub const Operand = extern struct { diff --git a/src/arch/mips/all.zig b/src/arch/mips/all.zig index ca5d90c..1cece6e 100644 --- a/src/arch/mips/all.zig +++ b/src/arch/mips/all.zig @@ -1,4 +1,8 @@ -const Operand = @import("operand.zig").Operand; +pub const OpMem = @import("op_mem.zig").OpMem; +pub const OpType = @import("op_type.zig").OpType; +pub const Register = @import("register.zig").Register; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { op_count: u8, diff --git a/src/arch/mos65xx/all.zig b/src/arch/mos65xx/all.zig index 3c08b9c..491f073 100644 --- a/src/arch/mos65xx/all.zig +++ b/src/arch/mos65xx/all.zig @@ -1,5 +1,8 @@ -const AddressMode = @import("address_mode.zig").AddressMode; -const Operand = @import("operand.zig").Operand; +pub const AddressMode = @import("address_mode.zig").AddressMode; +pub const OpType = @import("op_type.zig").OpType; +pub const Register = @import("register.zig").Register; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { am: AddressMode, diff --git a/src/arch/ppc/all.zig b/src/arch/ppc/all.zig index 33ecfb6..4b1c72c 100644 --- a/src/arch/ppc/all.zig +++ b/src/arch/ppc/all.zig @@ -1,6 +1,11 @@ -const Bc = @import("bc.zig").Bc; -const Bh = @import("bh.zig").Bh; -const Operand = @import("operand.zig").Operand; +pub const Bc = @import("bc.zig").Bc; +pub const Bh = @import("bh.zig").Bh; +pub const OpMem = @import("op_mem.zig").OpMem; +pub const OpType = @import("op_type.zig").OpType; +pub const Register = @import("register.zig").Register; +pub const OpCrx = @import("op_crx.zig").OpCrx; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { bc: Bc, diff --git a/src/arch/riscv/all.zig b/src/arch/riscv/all.zig index 3d6cb5e..0aedc3c 100644 --- a/src/arch/riscv/all.zig +++ b/src/arch/riscv/all.zig @@ -1,4 +1,7 @@ -const Operand = @import("operand.zig"); +pub const OpMem = @import("op_mem.zig").OpMem; +pub const OpType = @import("op_type.zig").OpType; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { need_effective_addr: bool, diff --git a/src/arch/sh/all.zig b/src/arch/sh/all.zig index e7102f7..a0a35f3 100644 --- a/src/arch/sh/all.zig +++ b/src/arch/sh/all.zig @@ -1,5 +1,14 @@ -const Insn = @import("insn.zig").Insn; -const Operand = @import("operand.zig").Operand; +pub const DspCc = @import("dsp_cc.zig").DspCc; +pub const DspInsn = @import("dsp_insn.zig").DspInsn; +pub const DspOperand = @import("dsp_operand.zig").DspOperand; +pub const Insn = @import("insn.zig").Insn; +pub const MemType = @import("mem_type.zig").MemType; +pub const OpMem = @import("op_mem.zig").OpMem; +pub const OpType = @import("op_type.zig").OpType; +pub const Register = @import("register.zig").Register; +pub const OpDsp = @import("op_dsp.zig").OpDsp; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { insn: Insn, diff --git a/src/arch/sparc/all.zig b/src/arch/sparc/all.zig index 59d8d7d..8e85b84 100644 --- a/src/arch/sparc/all.zig +++ b/src/arch/sparc/all.zig @@ -1,6 +1,10 @@ -const Cc = @import("cc.zig").Cc; -const Hint = @import("hint.zig").Hint; -const Operand = @import("operand.zig").Operand; +pub const Cc = @import("cc.zig").Cc; +pub const OpMem = @import("op_mem.zig").OpMem; +pub const OpType = @import("op_type.zig").OpType; +pub const Register = @import("register.zig").Register; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Hint = @import("hint.zig").Hint; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { cc: Cc, diff --git a/src/arch/sysz/all.zig b/src/arch/sysz/all.zig index 45e7063..151d451 100644 --- a/src/arch/sysz/all.zig +++ b/src/arch/sysz/all.zig @@ -1,5 +1,9 @@ -const Cc = @import("cc.zig").Cc; -const Operand = @import("operand.zig").Operand; +pub const Cc = @import("cc.zig").Cc; +pub const OpMem = @import("op_mem.zig").OpMem; +pub const OpType = @import("op_type.zig").OpType; +pub const Register = @import("register.zig").Register; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { cc: Cc, diff --git a/src/arch/tms320c64x/all.zig b/src/arch/tms320c64x/all.zig index 7a5a7b0..0b8bcf3 100644 --- a/src/arch/tms320c64x/all.zig +++ b/src/arch/tms320c64x/all.zig @@ -1,6 +1,9 @@ -const Operand = @import("operand.zig").Operand; -const Condition = @import("condition.zig").Condition; -const Funit = @import("funit.zig").Funit; +pub const OpMem = @import("op_mem.zig").OpMem; +pub const OpType = @import("op_type.zig").OpType; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; +pub const Condition = @import("condition.zig").Condition; +pub const Funit = @import("funit.zig").Funit; pub const Arch = extern struct { op_count: u8, diff --git a/src/arch/tricore/all.zig b/src/arch/tricore/all.zig index e9ebfc1..9754f76 100644 --- a/src/arch/tricore/all.zig +++ b/src/arch/tricore/all.zig @@ -1,4 +1,7 @@ -const Operand = @import("operand.zig").Operand; +pub const OpMem = @import("op_mem.zig").OpMem; +pub const OpType = @import("op_type.zig").OpType; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { op_count: u8, diff --git a/src/arch/wasm/all.zig b/src/arch/wasm/all.zig index ea308fa..5802bdf 100644 --- a/src/arch/wasm/all.zig +++ b/src/arch/wasm/all.zig @@ -1,4 +1,7 @@ -const Operand = @import("operand.zig").Operand; +pub const BrTable = @import("brtable.zig").BrTable; +pub const OpType = @import("op_type.zig").OpType; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { op_count: u8, diff --git a/src/arch/x86/all.zig b/src/arch/x86/all.zig index ebc7d62..24b1d98 100644 --- a/src/arch/x86/all.zig +++ b/src/arch/x86/all.zig @@ -10,14 +10,14 @@ pub const Flags = extern union { fpu_flags: u64, }; -const OpType = enum(cs.x86_op_type) { +pub const OpType = enum(cs.x86_op_type) { INVALID = 0, REG, IMM, MEM, }; -const Memory = extern struct { +pub const Memory = extern struct { segment: reg.Register, base: reg.Register, index: reg.Register, @@ -25,7 +25,7 @@ const Memory = extern struct { disp: i64, }; -const Instruction = extern union { +pub const Instruction = extern union { reg: reg.Register, imm: i64, mem: Memory, diff --git a/src/arch/x86/avx.zig b/src/arch/x86/avx.zig index 29ec210..ae98a62 100644 --- a/src/arch/x86/avx.zig +++ b/src/arch/x86/avx.zig @@ -1,4 +1,4 @@ -pub const cs = @import("capstone-c"); +const cs = @import("capstone-c"); pub const Cc = enum(cs.x86_avx_cc) { INVALID = 0, diff --git a/src/arch/x86/register.zig b/src/arch/x86/register.zig index 4fbf3e4..6e85f00 100644 --- a/src/arch/x86/register.zig +++ b/src/arch/x86/register.zig @@ -1,4 +1,4 @@ -pub const cs = @import("capstone-c"); +const cs = @import("capstone-c"); pub const Register = enum(cs.x86_reg) { INVALID = 0, diff --git a/src/arch/x86/sse.zig b/src/arch/x86/sse.zig index 11480e9..7b92ccb 100644 --- a/src/arch/x86/sse.zig +++ b/src/arch/x86/sse.zig @@ -1,4 +1,4 @@ -pub const cs = @import("capstone-c"); +const cs = @import("capstone-c"); pub const Cc = enum(cs.x86_sse_cc) { INVALID = 0, diff --git a/src/arch/x86/xop.zig b/src/arch/x86/xop.zig index 389d5af..72a9a1e 100644 --- a/src/arch/x86/xop.zig +++ b/src/arch/x86/xop.zig @@ -1,4 +1,4 @@ -pub const cs = @import("capstone-c"); +const cs = @import("capstone-c"); pub const Cc = enum(cs.x86_xop_cc) { INVALID = 0, diff --git a/src/arch/xcore/all.zig b/src/arch/xcore/all.zig index e0f0843..44042eb 100644 --- a/src/arch/xcore/all.zig +++ b/src/arch/xcore/all.zig @@ -1,4 +1,8 @@ -const Operand = @import("operand.zig").Operand; +pub const OpMem = @import("op_mem.zig").OpMem; +pub const OpType = @import("op_type.zig").OpType; +pub const Register = @import("register.zig").Register; +pub const Instruction = @import("instruction.zig").Instruction; +pub const Operand = @import("operand.zig").Operand; pub const Arch = extern struct { op_count: u8, diff --git a/src/enums.zig b/src/enums.zig index 259a067..e857d00 100644 --- a/src/enums.zig +++ b/src/enums.zig @@ -111,3 +111,12 @@ pub const Type = enum(cs.cs_opt_type) { UNSIGNED, NO_BRANCH_OFFSET, }; + +pub const Syntax = enum(cs.cs_opt_value) { + DEFAULT = 0, + INTEL, + ATT, + NOREGNAME, + MASM, + MOTOROLA, +}; diff --git a/src/impl.zig b/src/impl.zig index 479fef5..d636af7 100644 --- a/src/impl.zig +++ b/src/impl.zig @@ -4,15 +4,25 @@ pub const err = @import("error.zig"); pub const insn = @import("insn.zig"); pub const setup = @import("setup.zig"); pub const enums = @import("enums.zig"); +const iter = @import("iter.zig"); -pub const Iter = @import("iter.zig").Iter; +pub const IterUnmanaged = iter.IterUnmanaged; +pub const IterManaged = iter.IterManaged; pub const Handle = cs.csh; -pub fn version(major: ?*c_int, minor: ?*c_int) err.CapstoneError!void { - const maj = major orelse return {}; - const min = minor orelse return {}; +const Allocator = @import("std").mem.Allocator; +const SemanticVersion = @import("std").SemanticVersion; - return err.toError(cs.cs_version(@ptrCast(maj), @ptrCast(min))) orelse return; +pub fn version() SemanticVersion { + var major: c_uint = 0; + var minor: c_uint = 0; + + _ = cs.cs_version(@ptrCast(&major), @ptrCast(&minor)); + return SemanticVersion{ + .major = @intCast(major), + .minor = @intCast(minor), + .patch = 0, // Capstone does not provide patch version + }; } pub fn support(query: c_int) bool { @@ -60,33 +70,86 @@ pub fn disasm(handle: Handle, code: []const u8, address: usize, count: usize) er return ins[0..res_count]; } -pub fn free(ins: []insn.Insn) void { - cs.cs_free(@ptrCast(ins.ptr), ins.len); +/// Equivilent to cs_free +/// Only accepts `[]insn.Insn` or `*insn.Insn` types. +pub fn free(ins: anytype) void { + const type_info = @typeInfo(@TypeOf(ins)); + + if (type_info == .pointer) { + const pointer_type = type_info.pointer; + if (pointer_type.child != insn.Insn and pointer_type.size != .one) { + @compileError("`ins` type doesn't match `[]insn.Insn` or `*insn.Insn`"); + } + cs.cs_free(@ptrCast(ins), 1); + } else if (type_info == .array) { + const array_type = type_info.array; + if (array_type.child != insn.Insn) { + @compileError("`ins` type doesn't match `[]insn.Insn` or `*insn.Insn`"); + } + cs.cs_free(@ptrCast(ins.ptr), ins.len); + } else { + @compileError("`ins` type doesn't match `[]insn.Insn` or `*insn.Insn`"); + } } -pub fn malloc(handle: Handle) [*]insn.Insn { - return @ptrCast(cs.cs_malloc(handle)); +/// Equivilent to cs_malloc +pub fn malloc(handle: Handle) !*insn.Insn { + const ins: ?*insn.Insn = @ptrCast(cs.cs_malloc(handle)); + return if (ins) |i| return i else return error.OutOfMemory; } -/// Same as the normal Variant, but does the allocation for you. -pub fn disasmIterManaged(handle: Handle, code: []const u8, address: u64) Iter { - const ins = malloc(handle); - return Iter{ - .handle = handle, - .code = code, - .address = address, - .insn = ins, - }; +/// Create an Insn object using the provided allocator. +/// The owner takes responsibility of the pointer. +/// Must be destroyed with `destroyInsn`. +pub fn createInsn(allocator: Allocator, detail: bool) !*insn.Insn { + const ins = try allocator.create(insn.Insn); + if (detail) { + ins.detail = try allocator.create(insn.Detail); + } else { + ins.detail = null; + } + return ins; +} + +/// Creates a duplicate of the provided Insn object using the provided allocator. +/// The owner takes responsibility of the pointer. +/// Must be destroyed with `destroyInsn`. +pub fn dupeInsn(allocator: Allocator, ins: *const insn.Insn) !*insn.Insn { + var new_ins = try createInsn(allocator, ins.detail != null); + const new_detail = new_ins.detail; + new_ins.* = ins.*; + + if (ins.detail) |detail| { + new_detail.?.* = detail.*; + new_ins.detail = new_detail; + } + + return new_ins; +} + +/// Destroy an Insn object created with `createInsn`. +pub fn destroyInsn(allocator: Allocator, ins: *insn.Insn) void { + if (ins.detail) |detail| { + allocator.destroy(detail); + } + allocator.destroy(ins); +} + +/// Same as the normal Variant, but does the allocations using the provided allocator. +pub fn disasmIterManaged(allocator: Allocator, handle: Handle, code: []const u8, address: u64) !IterManaged { + return IterManaged.init(allocator, handle, code, address); } -/// Return an Iter object +/// Return an IterUnmanaged object /// Does not yet consume any element. -pub fn disasmIter(handle: Handle, code: []const u8, address: u64, ins: [*]insn.Insn) Iter { - return Iter{ +pub fn disasmIter(handle: Handle, code: []const u8, address: u64, ins: *insn.Insn) IterUnmanaged { + return IterUnmanaged{ .handle = handle, .code = code, + .original_code = code, + .original_address = address, .address = address, - .insn = ins, + .ins = ins, }; } @@ -102,35 +165,43 @@ pub fn groupName(handle: Handle, group_id: c_uint) [*:0]const u8 { return cs.cs_group_name(handle, group_id); } -pub fn insnGroup(handle: Handle, ins: []const insn.Insn, group_id: c_uint) bool { - return cs.cs_insn_group(handle, @ptrCast(ins.ptr), group_id); +pub fn insnGroup(handle: Handle, ins: *const insn.Insn, group_id: c_uint) bool { + return cs.cs_insn_group(handle, @ptrCast(ins), group_id); } -pub fn regRead(handle: Handle, ins: []const insn.Insn, reg_id: c_uint) bool { - return cs.cs_reg_read(handle, @ptrCast(ins.ptr), reg_id); +pub fn regRead(handle: Handle, ins: *const insn.Insn, reg_id: c_uint) bool { + return cs.cs_reg_read(handle, @ptrCast(ins), reg_id); } -pub fn regWrite(handle: Handle, ins: []const insn.Insn, reg_id: c_uint) bool { - return cs.cs_reg_write(handle, @ptrCast(ins.ptr), reg_id); +pub fn regWrite(handle: Handle, ins: *const insn.Insn, reg_id: c_uint) bool { + return cs.cs_reg_write(handle, @ptrCast(ins), reg_id); } -pub fn opCount(handle: Handle, ins: []const insn.Insn, op_type: c_uint) c_int { - return cs.cs_op_count(handle, @ptrCast(ins.ptr), op_type); +pub fn opCount(handle: Handle, ins: *const insn.Insn, op_type: c_uint) c_int { + return cs.cs_op_count(handle, @ptrCast(ins), op_type); } -pub fn opIndex(handle: Handle, ins: []const insn.Insn, op_type: c_uint, position: c_uint) c_int { - return cs.cs_op_index(handle, @ptrCast(ins.ptr), op_type, position); +pub fn opIndex(handle: Handle, ins: *const insn.Insn, op_type: c_uint, position: c_uint) c_int { + return cs.cs_op_index(handle, @ptrCast(ins), op_type, position); } +pub const Regs = [64]u16; pub fn regsAccess( handle: Handle, - ins: []const insn.Insn, - regs_read: [*]u16, - regs_read_count: [*]u8, - regs_write: [*]u16, - regs_write_count: [*]u8, + ins: *const insn.Insn, + regs_read: *Regs, + regs_read_count: *u8, + regs_write: *Regs, + regs_write_count: *u8, ) err.CapstoneError!void { - return err.toError(cs.cs_regs_access(handle, @ptrCast(ins.ptr), regs_read, regs_read_count, regs_write, regs_write_count)) orelse return; + return err.toError(cs.cs_regs_access( + handle, + @ptrCast(ins), + @ptrCast(regs_read[0..].ptr), + @ptrCast(regs_read_count), + @ptrCast(regs_write[0..].ptr), + @ptrCast(regs_write_count), + )) orelse return; } test { @@ -139,3 +210,52 @@ test { @import("std").testing.refAllDecls(@import("insn.zig")); @import("std").testing.refAllDecls(@import("setup.zig")); } + +test "malloc and free" { + var handle = try open(.X86, .@"16"); + defer close(&handle) catch {}; + const ins = try malloc(handle); + defer free(ins); + + const disass = try disasm(handle, "\x75\x14", 0x1000, 0); + defer free(disass); + try @import("std").testing.expectEqual(1, disass.len); +} + +test "disasm iter managed" { + const testing = @import("std").testing; + const allocator = testing.allocator; + + var handle = try open(.X86, .@"64"); + defer close(&handle) catch {}; + + var dis_iter = try disasmIterManaged(allocator, handle, "\x75\x14", 0x1000); + defer dis_iter.deinit(); + + const ins = dis_iter.next().?; + try testing.expect(ins.detail != null); + try testing.expectEqualStrings("jne", ins.mnemonic[0..3]); +} + +test "create, dupe and destroy Insn" { + const testing = @import("std").testing; + const allocator = testing.allocator; + + const ins = try createInsn(allocator, true); + defer destroyInsn(allocator, ins); + + ins.id = 0x1234; + ins.address = 0x1000; + ins.size = 2; + + ins.detail.?.groups_count = 2; + + const duped_ins = try dupeInsn(allocator, ins); + defer destroyInsn(allocator, duped_ins); + + try testing.expectEqual(ins.id, duped_ins.id); + try testing.expectEqual(ins.address, duped_ins.address); + try testing.expectEqual(ins.size, duped_ins.size); + try testing.expectEqual(ins.detail.?.groups_count, duped_ins.detail.?.groups_count); + try testing.expect(ins.detail != duped_ins.detail); +} diff --git a/src/insn.zig b/src/insn.zig index 82809e0..56c0b4a 100644 --- a/src/insn.zig +++ b/src/insn.zig @@ -8,7 +8,9 @@ pub const Detail = extern struct { groups: [8]u8, groups_count: u8, writeback: bool, - arch: arch.Arch, + arch: Arch, + + pub const Arch = arch.Arch; }; pub const Insn = extern struct { diff --git a/src/iter.zig b/src/iter.zig index 9de5a79..8e94bf9 100644 --- a/src/iter.zig +++ b/src/iter.zig @@ -1,33 +1,69 @@ const cs = @import("capstone-c"); +const insn = @import("insn.zig"); -const Insn = @import("insn.zig").Insn; +const Allocator = @import("std").mem.Allocator; const Handle = cs.csh; -// The Iterator for traversing the disassembler -pub const Iter = struct { +/// The Iterator for traversing the disassembler +pub const IterUnmanaged = struct { handle: Handle, code: []const u8, + original_code: []const u8, + original_address: u64, address: u64, - insn: [*]Insn, + ins: *insn.Insn, // Consumes the iterator and goes to the next - pub fn next(self: *Iter) ?*Insn { - if (cs.cs_disasm_iter(self.handle, @ptrCast(&self.code.ptr), @ptrCast(&self.code.len), &self.address, @ptrCast(self.insn))) { - const ret = &self.insn[0]; - ret.normalizeStrings(); - return ret; + pub fn next(self: *IterUnmanaged) ?*const insn.Insn { + if (cs.cs_disasm_iter(self.handle, @ptrCast(&self.code.ptr), @ptrCast(&self.code.len), &self.address, @ptrCast(self.ins))) { + self.ins.normalizeStrings(); + return self.ins; } else { return null; } } - // Clean up the iter - pub fn deinit(self: Iter) void { - cs.cs_free(@ptrCast(self.insn), 1); + pub fn reset(self: *IterUnmanaged) void { + self.address = self.original_address; + self.code = self.original_code; + } +}; + +/// The Iterator for traversing the disassembler, but **allocates** space using the provided `std.mem.Allocator`. +pub const IterManaged = struct { + allocator: Allocator, + unmanaged: IterUnmanaged, + ins: *insn.Insn, + + pub fn init(allocator: Allocator, handle: Handle, code: []const u8, address: u64) !IterManaged { + const ins = try allocator.create(insn.Insn); + ins.detail = try allocator.create(insn.Detail); + return .{ + .allocator = allocator, + .unmanaged = IterUnmanaged{ + .handle = handle, + .code = code, + .original_code = code, + .original_address = address, + .address = address, + .ins = ins, + }, + .ins = ins, + }; } - /// Returns the current address - pub fn address(self: Iter) u64 { - return self.address; + // Consumes the iterator and goes to the next + pub fn next(self: *IterManaged) ?*const insn.Insn { + return self.unmanaged.next(); + } + + pub fn reset(self: *IterManaged) void { + self.unmanaged.reset(); + } + + // Clean up the iter + pub fn deinit(self: IterManaged) void { + self.allocator.destroy(self.ins.detail.?); + self.allocator.destroy(self.ins); } }; diff --git a/src/setup.zig b/src/setup.zig index 60ac705..ab55a59 100644 --- a/src/setup.zig +++ b/src/setup.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const std = @import("std"); const insn = @import("insn.zig"); @@ -5,11 +6,17 @@ const err = @import("error.zig"); const cs = @import("capstone-c"); +// TODO: Use std.builtin.VaList later for windows x86_64, it's disabled currently in the zig's builtin since 0.14.1 +const VaList = if (builtin.os.tag == .windows and builtin.cpu.arch == .x86_64) + cs.__builtin_va_list +else + std.builtin.VaList; + pub const MallocFunction = ?*const fn (usize) callconv(.C) ?*anyopaque; pub const CallocFunction = ?*const fn (usize, usize) callconv(.C) ?*anyopaque; pub const ReallocFunction = ?*const fn (?*anyopaque, usize) callconv(.C) ?*anyopaque; pub const FreeFunction = ?*const fn (?*anyopaque) callconv(.C) void; -pub const VsnprintfFunction = ?*const fn ([*]u8, usize, [*]const u8, [*]std.builtin.VaList) callconv(.C) c_int; +pub const VsnprintfFunction = ?*const fn ([*]u8, usize, [*]const u8, [*]VaList) callconv(.C) c_int; var ALLOCATOR: ?std.mem.Allocator = null; @@ -20,7 +27,10 @@ var ALLOCATION_TABLE: AllocationTable = .{}; fn malloc(size: usize) callconv(.C) ?*anyopaque { if (ALLOCATOR) |alloc| { - const allocated = alloc.alignedAlloc(u8, 16, size) catch return null; + const allocated = if (builtin.zig_version.major == 0 and builtin.zig_version.minor == 14) + alloc.alignedAlloc(u8, 16, size) catch return null + else + alloc.alignedAlloc(u8, .@"16", size) catch return null; ALLOCATION_TABLE.put(alloc, @intFromPtr(allocated.ptr), allocated.len) catch @panic("OOM"); return @ptrCast(allocated.ptr); } else { @@ -74,7 +84,7 @@ fn free(ptr: ?*anyopaque) callconv(.C) void { } } -extern "C" fn vsnprintf([*c]u8, usize, [*c]const u8, [*c]std.builtin.VaList) callconv(.C) c_int; +extern "C" fn vsnprintf([*c]u8, usize, [*c]const u8, [*c]VaList) callconv(.C) c_int; /// Inits Capstone to be used with Zig pub fn initCapstone(alloc: std.mem.Allocator) err.CapstoneError!void { diff --git a/src/tests.zig b/src/tests.zig new file mode 100644 index 0000000..0aa02f3 --- /dev/null +++ b/src/tests.zig @@ -0,0 +1,5 @@ +test { + _ = @import("impl.zig"); + _ = @import("ManagedHandle.zig"); + @import("std").testing.refAllDecls(@This()); +}