166 lines
5.9 KiB
Zig
166 lines
5.9 KiB
Zig
const std = @import("std");
|
|
const mem = std.mem;
|
|
const zydis = @import("zydis").zydis;
|
|
|
|
const log = std.log.scoped(.disassembler);
|
|
const assert = std.debug.assert;
|
|
|
|
pub const InstructionIterator = struct {
|
|
decoder: zydis.ZydisDecoder,
|
|
bytes: []const u8,
|
|
instruction: zydis.ZydisDecodedInstruction,
|
|
operands: [zydis.ZYDIS_MAX_OPERAND_COUNT]zydis.ZydisDecodedOperand,
|
|
|
|
pub fn init(bytes: []const u8) InstructionIterator {
|
|
var decoder: zydis.ZydisDecoder = undefined;
|
|
const status = zydis.ZydisDecoderInit(
|
|
&decoder,
|
|
zydis.ZYDIS_MACHINE_MODE_LONG_64,
|
|
zydis.ZYDIS_STACK_WIDTH_64,
|
|
);
|
|
if (!zydis.ZYAN_SUCCESS(status)) @panic("Zydis decoder init failed");
|
|
return .{
|
|
.decoder = decoder,
|
|
.bytes = bytes,
|
|
.instruction = undefined,
|
|
.operands = undefined,
|
|
};
|
|
}
|
|
|
|
pub fn next(iterator: *InstructionIterator) ?BundledInstruction {
|
|
var status = zydis.ZydisDecoderDecodeFull(
|
|
&iterator.decoder,
|
|
iterator.bytes.ptr,
|
|
iterator.bytes.len,
|
|
&iterator.instruction,
|
|
&iterator.operands,
|
|
);
|
|
var address: u64 = @intFromPtr(iterator.bytes.ptr);
|
|
|
|
while (!zydis.ZYAN_SUCCESS(status)) {
|
|
// TODO: handle common padding bytes
|
|
switch (status) {
|
|
zydis.ZYDIS_STATUS_NO_MORE_DATA => {
|
|
log.info("next: Got status: NO_MORE_DATA. Iterator completed.", .{});
|
|
return null;
|
|
},
|
|
zydis.ZYDIS_STATUS_ILLEGAL_LOCK => log.warn("next: Got status: ILLEGAL_LOCK. " ++
|
|
"Byte stepping, to find next valid instruction begin", .{}),
|
|
zydis.ZYDIS_STATUS_DECODING_ERROR => log.warn("next: Got status: DECODING_ERROR. " ++
|
|
"Byte stepping, to find next valid instruction begin", .{}),
|
|
else => log.warn("next: Got unknown status: 0x{x}. Byte stepping, to find next " ++
|
|
"valid instruction begin", .{status}),
|
|
}
|
|
// TODO: add a flag to instead return an error
|
|
log.debug(
|
|
"next: instruction length: {}, address: 0x{x}, bytes: 0x{x}",
|
|
.{
|
|
iterator.instruction.length,
|
|
address,
|
|
iterator.bytes[0..iterator.instruction.length],
|
|
},
|
|
);
|
|
|
|
iterator.bytes = iterator.bytes[1..];
|
|
status = zydis.ZydisDecoderDecodeFull(
|
|
&iterator.decoder,
|
|
iterator.bytes.ptr,
|
|
iterator.bytes.len,
|
|
&iterator.instruction,
|
|
&iterator.operands,
|
|
);
|
|
address = @intFromPtr(iterator.bytes.ptr);
|
|
}
|
|
|
|
iterator.bytes = iterator.bytes[iterator.instruction.length..];
|
|
return .{
|
|
.address = address,
|
|
.instruction = &iterator.instruction,
|
|
.operands = iterator.operands[0..iterator.instruction.operand_count_visible],
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const BundledInstruction = struct {
|
|
address: u64,
|
|
instruction: *const zydis.ZydisDecodedInstruction,
|
|
operands: []const zydis.ZydisDecodedOperand,
|
|
};
|
|
|
|
/// Disassemble `bytes` and format them into the given buffer. Useful for error reporting or
|
|
/// debugging purposes. On error return an empty string.
|
|
/// This function is not threadsafe.
|
|
pub fn formatBytes(bytes: []const u8) []u8 {
|
|
const instr = disassembleInstruction(bytes) orelse return "";
|
|
return formatInstruction(instr);
|
|
}
|
|
|
|
/// Format the given instruction into the buffer. On error return an empty string.
|
|
/// This function is not threadsafe.
|
|
pub fn formatInstruction(instruction: BundledInstruction) []u8 {
|
|
// Static variable to initialize the formatter only once and have a valid address for the
|
|
// buffer.
|
|
const static = struct {
|
|
var initialized = false;
|
|
var formatter: zydis.ZydisFormatter = undefined;
|
|
var buffer: [256]u8 = undefined;
|
|
};
|
|
if (!static.initialized) {
|
|
const status = zydis.ZydisFormatterInit(&static.formatter, zydis.ZYDIS_FORMATTER_STYLE_ATT);
|
|
if (!zydis.ZYAN_SUCCESS(status)) @panic("Zydis formatter init failed");
|
|
}
|
|
const status = zydis.ZydisFormatterFormatInstruction(
|
|
&static.formatter,
|
|
instruction.instruction,
|
|
instruction.operands.ptr,
|
|
instruction.instruction.operand_count_visible,
|
|
&static.buffer,
|
|
static.buffer.len,
|
|
instruction.address,
|
|
null,
|
|
);
|
|
if (zydis.ZYAN_SUCCESS(status)) {
|
|
return mem.sliceTo(&static.buffer, 0);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Disassemble the first instruction at bytes. On error return `null`.
|
|
/// This function is not threadsafe.
|
|
pub fn disassembleInstruction(bytes: []const u8) ?BundledInstruction {
|
|
// Static variable to initialize the decoder only once and have a valid address for the
|
|
// instruction and operands.
|
|
const static = struct {
|
|
var initialized = false;
|
|
var decoder: zydis.ZydisDecoder = undefined;
|
|
var instruction: zydis.ZydisDecodedInstruction = undefined;
|
|
var operands: [zydis.ZYDIS_MAX_OPERAND_COUNT]zydis.ZydisDecodedOperand = undefined;
|
|
};
|
|
if (!static.initialized) {
|
|
const status = zydis.ZydisDecoderInit(
|
|
&static.decoder,
|
|
zydis.ZYDIS_MACHINE_MODE_LONG_64,
|
|
zydis.ZYDIS_STACK_WIDTH_64,
|
|
);
|
|
if (!zydis.ZYAN_SUCCESS(status)) @panic("Zydis decoder init failed");
|
|
static.initialized = true;
|
|
}
|
|
const status = zydis.ZydisDecoderDecodeFull(
|
|
&static.decoder,
|
|
bytes.ptr,
|
|
bytes.len,
|
|
&static.instruction,
|
|
&static.operands,
|
|
);
|
|
if (zydis.ZYAN_SUCCESS(status)) {
|
|
return .{
|
|
.address = @intFromPtr(bytes.ptr),
|
|
.instruction = &static.instruction,
|
|
.operands = &static.operands,
|
|
};
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|