Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
320a085
updated for zig-master(0.15.0-dev.646+ef35c3d5f)
IamSanjid May 28, 2025
57dee56
fix some compilation errors
IamSanjid May 28, 2025
8d79486
cs_opt_value zig enum
IamSanjid May 28, 2025
f55459d
cs_opt_value zig enum exposed
IamSanjid May 28, 2025
b5a3769
added more enum variants
IamSanjid May 28, 2025
36ecfb5
expose capstone-c
IamSanjid May 28, 2025
3e0f5b8
remove unnecessary enums
IamSanjid May 28, 2025
a704610
expose capstone-c
IamSanjid May 28, 2025
93044d1
expose capstone-c properly
IamSanjid May 28, 2025
6f3fb09
expose Insn and Detail
IamSanjid May 28, 2025
13641db
Expose Detail, Arch, Insn
IamSanjid May 28, 2025
1bc2966
remove unnecessary expose
IamSanjid May 28, 2025
74352fd
fix naming enums.OptionsType
IamSanjid May 28, 2025
2bb1a02
Iter with reset
IamSanjid May 29, 2025
fd7c930
Change, restored some stuff, support for zig 0.14.1
IamSanjid May 29, 2025
1934069
using capstone pre-release 5.0.1-2 and some minor changes
IamSanjid May 30, 2025
3746dd4
Update build.zig.zon
IamSanjid May 30, 2025
2e9786e
changed [*]insn.Insn to *insn.Insn where it's appropriate
IamSanjid May 30, 2025
767e955
expose arch
IamSanjid May 30, 2025
dbde91c
some cleanups and changes to adapt current Iter/Insn alloc impl
IamSanjid May 30, 2025
facb0c4
keep the global function names close to as original capstone functions
IamSanjid May 30, 2025
d1649ef
Expose all the things of arch
IamSanjid May 31, 2025
42c1072
IterUnmanaged, createInsn, dupeInsn, destroyInsn
IamSanjid May 31, 2025
ef38def
update ci to 0.14.1
SoraTenshi Jun 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ 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,
});
// expose it for headers...
const capstone_c_mod = capstone_c.createModule();

const mod = b.addModule("capstone-bindings-zig", .{
.root_source_file = b.path("capstone.zig"),
Expand All @@ -25,7 +27,7 @@ pub fn build(b: *std.Build) void {
.imports = &.{
.{
.name = "capstone-c",
.module = capstone_c.createModule(),
.module = capstone_c_mod,
},
},
});
Expand All @@ -34,13 +36,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");
Expand Down
7 changes: 4 additions & 3 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -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?ref=main#6035a194bea8472c463345b86c3ef9333490ecfe",
Comment thread
IamSanjid marked this conversation as resolved.
Outdated
.hash = "capstone-5.0.1-tmNUgTRHAAC1nFVy0colCWwUSRm05SWdD-4kU5MK4ZpH",
},
},

Expand Down
7 changes: 7 additions & 0 deletions capstone.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const impl = @import("src/impl.zig");
pub const Iter = impl.Iter;
pub const IterManaged = impl.IterManaged;
pub const Handle = impl.Handle;
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;
Expand Down Expand Up @@ -42,3 +45,7 @@ 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");

pub const c = @import("capstone-c");
136 changes: 136 additions & 0 deletions src/ManagedHandle.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
const builtin = @import("builtin");
const std = @import("std");

const cs = @import("capstone-c");

const err = @import("error.zig");
const insn = @import("insn.zig");
const setup = @import("setup.zig");
const enums = @import("enums.zig");
const impl = @import("impl.zig");

const iter = @import("iter.zig");

const Iter = iter.Iter;
const IterManaged = iter.IterManaged;

native: impl.Handle,

// To avoid heap allocations...
const TmpStorage = blk: {
if (builtin.single_threaded) {
break :blk 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 {
break :blk 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,
/// 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,
};
};

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.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 };
}

/// 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 {
return Iter.init(self.native, code, address, @ptrCast(TmpStorage.getIns()));
}

pub fn deinit(self: *Self) void {
impl.close(&self.native) catch |e| {
std.debug.print("Failed to close handle: {any}\n", .{impl.strerror(e)});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use debug print here, instead:

std.io.getStdErr().writer().print("Failed to close handle: {any}\n", .{impl.strerror(e)});

@IamSanjid IamSanjid May 30, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm so std.io.getStdErr().writer().print("Failed to close handle: {any}\n", .{impl.strerror(e)}) catch {}; since the print returns error, I don't think a deinit function should return any error?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct. :D

};
}

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]);
}
9 changes: 9 additions & 0 deletions src/enums.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
22 changes: 7 additions & 15 deletions src/impl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ 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 Iter = iter.Iter;
pub const IterManaged = iter.IterManaged;
pub const Handle = cs.csh;

pub fn version(major: ?*c_int, minor: ?*c_int) err.CapstoneError!void {
Expand Down Expand Up @@ -69,25 +71,14 @@ pub fn malloc(handle: Handle) [*]insn.Insn {
}

/// 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,
};
pub fn disasmIterManaged(handle: Handle, code: []const u8, address: u64) IterManaged {
return IterManaged.init(handle, code, address);
}

/// Return an Iter object
/// Does not yet consume any element.
pub fn disasmIter(handle: Handle, code: []const u8, address: u64, ins: [*]insn.Insn) Iter {
return Iter{
.handle = handle,
.code = code,
.address = address,
.insn = ins,
};
return Iter.init(handle, code, address, ins);
}

pub fn regName(handle: Handle, reg_id: c_uint) [*:0]const u8 {
Expand Down Expand Up @@ -138,4 +129,5 @@ test {
@import("std").testing.refAllDecls(@import("error.zig"));
@import("std").testing.refAllDecls(@import("insn.zig"));
@import("std").testing.refAllDecls(@import("setup.zig"));
@import("std").testing.refAllDecls(@import("ManagedHandle.zig"));
}
4 changes: 3 additions & 1 deletion src/insn.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment thread
SoraTenshi marked this conversation as resolved.
};

pub const Insn = extern struct {
Expand Down
50 changes: 43 additions & 7 deletions src/iter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,26 @@ const cs = @import("capstone-c");
const Insn = @import("insn.zig").Insn;
const Handle = cs.csh;

// The Iterator for traversing the disassembler
/// The Iterator for traversing the disassembler
pub const Iter = struct {
Comment thread
IamSanjid marked this conversation as resolved.
Outdated
handle: Handle,
code: []const u8,
original_code: []const u8,
original_address: u64,
address: u64,
insn: [*]Insn,

pub fn init(handle: Handle, code: []const u8, address: u64, insn: [*]Insn) Iter {
return Iter{
.handle = handle,
.code = code,
.original_code = code,
.original_address = address,
.address = address,
.insn = insn,
};
}

Comment thread
IamSanjid marked this conversation as resolved.
Outdated
// 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))) {
Expand All @@ -21,13 +34,36 @@ pub const Iter = struct {
}
}

// Clean up the iter
pub fn deinit(self: Iter) void {
cs.cs_free(@ptrCast(self.insn), 1);
pub fn reset(self: *Iter) void {
self.address = self.original_address;
self.code = self.original_code;
}
};

/// The Iterator for traversing the disassembler, but **allocates** space using capstone malloc.
pub const IterManaged = struct {
inner: Iter,
insn: [*]Insn,

pub fn init(handle: Handle, code: []const u8, address: u64) IterManaged {
const insn: [*]Insn = @ptrCast(cs.cs_malloc(handle));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this seems a bit fishy, i believe i have forgotten to implement an abstraction for cs_malloc that returns either an error or the pointer (which must be valid).
in src/impl.zig there's a malloc function, this we should probably fix in case of OOM.

@IamSanjid IamSanjid May 30, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the cs_malloc doesn't return any error... https://github.com/capstone-engine/capstone/blob/280b749e84adca4177b7525504e55be4d8c74e44/cs.c#L1429 ow wait I mean it doesn't return the error directly but it does set the error to the handle, hmm ye the managed iter needs to return error too

@SoraTenshi SoraTenshi May 30, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was thinking about wrapping the type for it.

pub fn allocInsn(handle: Handle) ![*]Insn {
    const insn: ?[*]Insn = @ptrCast(cs.cs_malloc(handle));
    return if(insn) |i| i else error.OutOfMemory;
}

@IamSanjid IamSanjid May 30, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ye we should just replace the

pub fn malloc(handle: Handle) [*]insn.Insn {
    return @ptrCast(cs.cs_malloc(handle));
}

this malloc really doesn't make sense from the user space, since all it does is allocate space for only one insn.Insn

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was thinking about wrapping the type for it.

pub fn allocInsn(handle: Handle) ![*]Insn {
    const insn: ?[*]Insn = @ptrCast(cs.cs_malloc(handle));
    return if(insn) |i| i else error.OutOfMemory;
}

and by the way shouldn't we return a *Insn the many pointer [*] variant doesn't make sense here, coz it will always return one Insn I will try to convert all the [*] many pointer variants to one pointer variant where it's appropiate

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not tooo familiar with capstone, hence i tried and kept it as close to C as possible.
But yeah, if it is always a single instance of an Insn, then i agree with you.

return .{
.inner = Iter.init(handle, code, address, insn),
.insn = insn,
};
}

// Consumes the iterator and goes to the next
pub fn next(self: *IterManaged) ?*Insn {
return self.inner.next();
}

/// Returns the current address
pub fn address(self: Iter) u64 {
return self.address;
pub fn reset(self: *IterManaged) void {
self.inner.reset();
}

// Clean up the iter
pub fn deinit(self: IterManaged) void {
cs.cs_free(@ptrCast(self.insn), 1);
}
};
Loading