Zig binding

* zig binding - sample added
* zig build CI
* split mingw (shared/static) CI/CD
* unicorn log added
* build C/C++ samples
This commit is contained in:
Matheus C. França
2023-03-31 13:31:02 -03:00
committed by Matheus Catarino França
parent 0619deeafd
commit 4fb4b3e4b0
13 changed files with 762 additions and 7 deletions

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
@echo off
zig cc -fno-sanitize=all %*

3
bindings/zig/tools/zigcc.sh Executable file
View File

@@ -0,0 +1,3 @@
#! /usr/bin/env bash
`which zig` cc -fno-sanitize=all $@

View File

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