authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-09-13 17:54:39+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-09-13 21:51:43+02:00
log7a16a97671af5ca324150e4f2f9bdf12cb3cc2cd
tree53567459bde4d459da42ecfb15769ad2b0c99d2a
parent9de0df76a80947cadd50c716763604e603199dcc

x86_64: add simple disassembler interface to the encoder


1 files changed, 474 insertions(+), 0 deletions(-)

src/arch/x86_64/Disassembler.zig created+474
...@@ -0,0 +1,474 @@
1const Disassembler = @This();
2
3const std = @import("std");
4const assert = std.debug.assert;
5const math = std.math;
6
7const bits = @import("bits.zig");
8const encoder = @import("encoder.zig");
9
10const Encoding = @import("Encoding.zig");
11const Immediate = bits.Immediate;
12const Instruction = encoder.Instruction;
13const LegacyPrefixes = encoder.LegacyPrefixes;
14const Memory = bits.Memory;
15const Register = bits.Register;
16const Rex = encoder.Rex;
17
18pub const Error = error{
19 EndOfStream,
20 LegacyPrefixAfterRex,
21 UnknownOpcode,
22 Todo,
23};
24
25code: []const u8,
26pos: usize = 0,
27
28pub fn init(code: []const u8) Disassembler {
29 return .{ .code = code };
30}
31
32pub fn next(dis: *Disassembler) Error!?Instruction {
33 const prefixes = dis.parsePrefixes() catch |err| switch (err) {
34 error.EndOfStream => return null,
35 else => |e| return e,
36 };
37
38 const enc = try dis.parseEncoding(prefixes) orelse return error.UnknownOpcode;
39 switch (enc.data.op_en) {
40 .np => return inst(enc, .{}),
41 .d, .i => {
42 const imm = try dis.parseImm(enc.data.ops[0]);
43 return inst(enc, .{
44 .op1 = .{ .imm = imm },
45 });
46 },
47 .zi => {
48 const imm = try dis.parseImm(enc.data.ops[1]);
49 return inst(enc, .{
50 .op1 = .{ .reg = Register.rax.toBitSize(enc.data.ops[0].regBitSize()) },
51 .op2 = .{ .imm = imm },
52 });
53 },
54 .o, .oi => {
55 const reg_low_enc = @as(u3, @truncate(dis.code[dis.pos - 1]));
56 const op2: Instruction.Operand = if (enc.data.op_en == .oi) .{
57 .imm = try dis.parseImm(enc.data.ops[1]),
58 } else .none;
59 return inst(enc, .{
60 .op1 = .{ .reg = parseGpRegister(reg_low_enc, prefixes.rex.b, prefixes.rex, enc.data.ops[0].regBitSize()) },
61 .op2 = op2,
62 });
63 },
64 .m, .mi, .m1, .mc => {
65 const modrm = try dis.parseModRmByte();
66 const act_enc = Encoding.findByOpcode(enc.opcode(), .{
67 .legacy = prefixes.legacy,
68 .rex = prefixes.rex,
69 }, modrm.op1) orelse return error.UnknownOpcode;
70 const sib = if (modrm.sib()) try dis.parseSibByte() else null;
71
72 if (modrm.direct()) {
73 const op2: Instruction.Operand = switch (act_enc.data.op_en) {
74 .mi => .{ .imm = try dis.parseImm(act_enc.data.ops[1]) },
75 .m1 => .{ .imm = Immediate.u(1) },
76 .mc => .{ .reg = .cl },
77 .m => .none,
78 else => unreachable,
79 };
80 return inst(act_enc, .{
81 .op1 = .{ .reg = parseGpRegister(modrm.op2, prefixes.rex.b, prefixes.rex, act_enc.data.ops[0].regBitSize()) },
82 .op2 = op2,
83 });
84 }
85
86 const disp = try dis.parseDisplacement(modrm, sib);
87 const op2: Instruction.Operand = switch (act_enc.data.op_en) {
88 .mi => .{ .imm = try dis.parseImm(act_enc.data.ops[1]) },
89 .m1 => .{ .imm = Immediate.u(1) },
90 .mc => .{ .reg = .cl },
91 .m => .none,
92 else => unreachable,
93 };
94
95 if (modrm.rip()) {
96 return inst(act_enc, .{
97 .op1 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(act_enc.data.ops[0].memBitSize()), disp) },
98 .op2 = op2,
99 });
100 }
101
102 const scale_index = if (sib) |info| info.scaleIndex(prefixes.rex) else null;
103 const base = if (sib) |info|
104 info.baseReg(modrm, prefixes)
105 else
106 parseGpRegister(modrm.op2, prefixes.rex.b, prefixes.rex, 64);
107 return inst(act_enc, .{
108 .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(act_enc.data.ops[0].memBitSize()), .{
109 .base = if (base) |base_reg| .{ .reg = base_reg } else .none,
110 .scale_index = scale_index,
111 .disp = disp,
112 }) },
113 .op2 = op2,
114 });
115 },
116 .fd => {
117 const seg = segmentRegister(prefixes.legacy);
118 const offset = try dis.parseOffset();
119 return inst(enc, .{
120 .op1 = .{ .reg = Register.rax.toBitSize(enc.data.ops[0].regBitSize()) },
121 .op2 = .{ .mem = Memory.moffs(seg, offset) },
122 });
123 },
124 .td => {
125 const seg = segmentRegister(prefixes.legacy);
126 const offset = try dis.parseOffset();
127 return inst(enc, .{
128 .op1 = .{ .mem = Memory.moffs(seg, offset) },
129 .op2 = .{ .reg = Register.rax.toBitSize(enc.data.ops[1].regBitSize()) },
130 });
131 },
132 .mr, .mri, .mrc => {
133 const modrm = try dis.parseModRmByte();
134 const sib = if (modrm.sib()) try dis.parseSibByte() else null;
135 const src_bit_size = enc.data.ops[1].regBitSize();
136
137 if (modrm.direct()) {
138 return inst(enc, .{
139 .op1 = .{ .reg = parseGpRegister(modrm.op2, prefixes.rex.b, prefixes.rex, enc.data.ops[0].regBitSize()) },
140 .op2 = .{ .reg = parseGpRegister(modrm.op1, prefixes.rex.x, prefixes.rex, src_bit_size) },
141 });
142 }
143
144 const dst_bit_size = enc.data.ops[0].memBitSize();
145 const disp = try dis.parseDisplacement(modrm, sib);
146 const op3: Instruction.Operand = switch (enc.data.op_en) {
147 .mri => .{ .imm = try dis.parseImm(enc.data.ops[2]) },
148 .mrc => .{ .reg = .cl },
149 .mr => .none,
150 else => unreachable,
151 };
152
153 if (modrm.rip()) {
154 return inst(enc, .{
155 .op1 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(dst_bit_size), disp) },
156 .op2 = .{ .reg = parseGpRegister(modrm.op1, prefixes.rex.r, prefixes.rex, src_bit_size) },
157 .op3 = op3,
158 });
159 }
160
161 const scale_index = if (sib) |info| info.scaleIndex(prefixes.rex) else null;
162 const base = if (sib) |info|
163 info.baseReg(modrm, prefixes)
164 else
165 parseGpRegister(modrm.op2, prefixes.rex.b, prefixes.rex, 64);
166 return inst(enc, .{
167 .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(dst_bit_size), .{
168 .base = if (base) |base_reg| .{ .reg = base_reg } else .none,
169 .scale_index = scale_index,
170 .disp = disp,
171 }) },
172 .op2 = .{ .reg = parseGpRegister(modrm.op1, prefixes.rex.r, prefixes.rex, src_bit_size) },
173 .op3 = op3,
174 });
175 },
176 .rm, .rmi => {
177 const modrm = try dis.parseModRmByte();
178 const sib = if (modrm.sib()) try dis.parseSibByte() else null;
179 const dst_bit_size = enc.data.ops[0].regBitSize();
180
181 if (modrm.direct()) {
182 const op3: Instruction.Operand = switch (enc.data.op_en) {
183 .rm => .none,
184 .rmi => .{ .imm = try dis.parseImm(enc.data.ops[2]) },
185 else => unreachable,
186 };
187 return inst(enc, .{
188 .op1 = .{ .reg = parseGpRegister(modrm.op1, prefixes.rex.x, prefixes.rex, dst_bit_size) },
189 .op2 = .{ .reg = parseGpRegister(modrm.op2, prefixes.rex.b, prefixes.rex, enc.data.ops[1].regBitSize()) },
190 .op3 = op3,
191 });
192 }
193
194 const src_bit_size = if (enc.data.ops[1] == .m) dst_bit_size else enc.data.ops[1].memBitSize();
195 const disp = try dis.parseDisplacement(modrm, sib);
196 const op3: Instruction.Operand = switch (enc.data.op_en) {
197 .rmi => .{ .imm = try dis.parseImm(enc.data.ops[2]) },
198 .rm => .none,
199 else => unreachable,
200 };
201
202 if (modrm.rip()) {
203 return inst(enc, .{
204 .op1 = .{ .reg = parseGpRegister(modrm.op1, prefixes.rex.r, prefixes.rex, dst_bit_size) },
205 .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(src_bit_size), disp) },
206 .op3 = op3,
207 });
208 }
209
210 const scale_index = if (sib) |info| info.scaleIndex(prefixes.rex) else null;
211 const base = if (sib) |info|
212 info.baseReg(modrm, prefixes)
213 else
214 parseGpRegister(modrm.op2, prefixes.rex.b, prefixes.rex, 64);
215 return inst(enc, .{
216 .op1 = .{ .reg = parseGpRegister(modrm.op1, prefixes.rex.r, prefixes.rex, dst_bit_size) },
217 .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(src_bit_size), .{
218 .base = if (base) |base_reg| .{ .reg = base_reg } else .none,
219 .scale_index = scale_index,
220 .disp = disp,
221 }) },
222 .op3 = op3,
223 });
224 },
225 .rm0, .vmi, .rvm, .rvmr, .rvmi, .mvr => unreachable, // TODO
226 }
227}
228
229fn inst(encoding: Encoding, args: struct {
230 prefix: Instruction.Prefix = .none,
231 op1: Instruction.Operand = .none,
232 op2: Instruction.Operand = .none,
233 op3: Instruction.Operand = .none,
234 op4: Instruction.Operand = .none,
235}) Instruction {
236 var i = Instruction{ .encoding = encoding, .prefix = args.prefix, .ops = .{
237 args.op1,
238 args.op2,
239 args.op3,
240 args.op4,
241 } };
242 return i;
243}
244
245const Prefixes = struct {
246 legacy: LegacyPrefixes = .{},
247 rex: Rex = .{},
248 // TODO add support for VEX prefix
249};
250
251fn parsePrefixes(dis: *Disassembler) !Prefixes {
252 const rex_prefix_mask: u4 = 0b0100;
253 var stream = std.io.fixedBufferStream(dis.code[dis.pos..]);
254 const reader = stream.reader();
255
256 var res: Prefixes = .{};
257
258 while (true) {
259 const next_byte = try reader.readByte();
260 dis.pos += 1;
261
262 switch (next_byte) {
263 0xf0, 0xf2, 0xf3, 0x2e, 0x36, 0x26, 0x64, 0x65, 0x3e, 0x66, 0x67 => {
264 // Legacy prefix
265 if (res.rex.present) return error.LegacyPrefixAfterRex;
266 switch (next_byte) {
267 0xf0 => res.legacy.prefix_f0 = true,
268 0xf2 => res.legacy.prefix_f2 = true,
269 0xf3 => res.legacy.prefix_f3 = true,
270 0x2e => res.legacy.prefix_2e = true,
271 0x36 => res.legacy.prefix_36 = true,
272 0x26 => res.legacy.prefix_26 = true,
273 0x64 => res.legacy.prefix_64 = true,
274 0x65 => res.legacy.prefix_65 = true,
275 0x3e => res.legacy.prefix_3e = true,
276 0x66 => res.legacy.prefix_66 = true,
277 0x67 => res.legacy.prefix_67 = true,
278 else => unreachable,
279 }
280 },
281 else => {
282 if (rex_prefix_mask == @as(u4, @truncate(next_byte >> 4))) {
283 // REX prefix
284 res.rex.w = next_byte & 0b1000 != 0;
285 res.rex.r = next_byte & 0b100 != 0;
286 res.rex.x = next_byte & 0b10 != 0;
287 res.rex.b = next_byte & 0b1 != 0;
288 res.rex.present = true;
289 continue;
290 }
291
292 // TODO VEX prefix
293
294 dis.pos -= 1;
295 break;
296 },
297 }
298 }
299
300 return res;
301}
302
303fn parseEncoding(dis: *Disassembler, prefixes: Prefixes) !?Encoding {
304 const o_mask: u8 = 0b1111_1000;
305
306 var opcode: [3]u8 = .{ 0, 0, 0 };
307 var stream = std.io.fixedBufferStream(dis.code[dis.pos..]);
308 const reader = stream.reader();
309
310 comptime var opc_count = 0;
311 inline while (opc_count < 3) : (opc_count += 1) {
312 const byte = try reader.readByte();
313 opcode[opc_count] = byte;
314 dis.pos += 1;
315
316 if (byte == 0x0f) {
317 // Multi-byte opcode
318 } else if (opc_count > 0) {
319 // Multi-byte opcode
320 if (Encoding.findByOpcode(opcode[0 .. opc_count + 1], .{
321 .legacy = prefixes.legacy,
322 .rex = prefixes.rex,
323 }, null)) |mnemonic| {
324 return mnemonic;
325 }
326 } else {
327 // Single-byte opcode
328 if (Encoding.findByOpcode(opcode[0..1], .{
329 .legacy = prefixes.legacy,
330 .rex = prefixes.rex,
331 }, null)) |mnemonic| {
332 return mnemonic;
333 } else {
334 // Try O* encoding
335 return Encoding.findByOpcode(&.{opcode[0] & o_mask}, .{
336 .legacy = prefixes.legacy,
337 .rex = prefixes.rex,
338 }, null);
339 }
340 }
341 }
342 return null;
343}
344
345fn parseGpRegister(low_enc: u3, is_extended: bool, rex: Rex, bit_size: u64) Register {
346 const reg_id: u4 = @as(u4, @intCast(@intFromBool(is_extended))) << 3 | low_enc;
347 const reg = @as(Register, @enumFromInt(reg_id)).toBitSize(bit_size);
348 return switch (reg) {
349 .spl => if (rex.present or rex.isSet()) .spl else .ah,
350 .dil => if (rex.present or rex.isSet()) .dil else .bh,
351 .bpl => if (rex.present or rex.isSet()) .bpl else .ch,
352 .sil => if (rex.present or rex.isSet()) .sil else .dh,
353 else => reg,
354 };
355}
356
357fn parseImm(dis: *Disassembler, kind: Encoding.Op) !Immediate {
358 var stream = std.io.fixedBufferStream(dis.code[dis.pos..]);
359 var creader = std.io.countingReader(stream.reader());
360 const reader = creader.reader();
361 const imm = switch (kind) {
362 .imm8s, .rel8 => Immediate.s(try reader.readInt(i8, .Little)),
363 .imm16s, .rel16 => Immediate.s(try reader.readInt(i16, .Little)),
364 .imm32s, .rel32 => Immediate.s(try reader.readInt(i32, .Little)),
365 .imm8 => Immediate.u(try reader.readInt(u8, .Little)),
366 .imm16 => Immediate.u(try reader.readInt(u16, .Little)),
367 .imm32 => Immediate.u(try reader.readInt(u32, .Little)),
368 .imm64 => Immediate.u(try reader.readInt(u64, .Little)),
369 else => unreachable,
370 };
371 dis.pos += creader.bytes_read;
372 return imm;
373}
374
375fn parseOffset(dis: *Disassembler) !u64 {
376 var stream = std.io.fixedBufferStream(dis.code[dis.pos..]);
377 const reader = stream.reader();
378 const offset = try reader.readInt(u64, .Little);
379 dis.pos += 8;
380 return offset;
381}
382
383const ModRm = packed struct {
384 mod: u2,
385 op1: u3,
386 op2: u3,
387
388 inline fn direct(self: ModRm) bool {
389 return self.mod == 0b11;
390 }
391
392 inline fn rip(self: ModRm) bool {
393 return self.mod == 0 and self.op2 == 0b101;
394 }
395
396 inline fn sib(self: ModRm) bool {
397 return !self.direct() and self.op2 == 0b100;
398 }
399};
400
401fn parseModRmByte(dis: *Disassembler) !ModRm {
402 if (dis.code[dis.pos..].len == 0) return error.EndOfStream;
403 const modrm_byte = dis.code[dis.pos];
404 dis.pos += 1;
405 const mod: u2 = @as(u2, @truncate(modrm_byte >> 6));
406 const op1: u3 = @as(u3, @truncate(modrm_byte >> 3));
407 const op2: u3 = @as(u3, @truncate(modrm_byte));
408 return ModRm{ .mod = mod, .op1 = op1, .op2 = op2 };
409}
410
411fn segmentRegister(prefixes: LegacyPrefixes) Register {
412 if (prefixes.prefix_2e) return .cs;
413 if (prefixes.prefix_36) return .ss;
414 if (prefixes.prefix_26) return .es;
415 if (prefixes.prefix_64) return .fs;
416 if (prefixes.prefix_65) return .gs;
417 return .ds;
418}
419
420const Sib = packed struct {
421 scale: u2,
422 index: u3,
423 base: u3,
424
425 fn scaleIndex(self: Sib, rex: Rex) ?Memory.ScaleIndex {
426 if (self.index == 0b100 and !rex.x) return null;
427 return .{
428 .scale = @as(u4, 1) << self.scale,
429 .index = parseGpRegister(self.index, rex.x, rex, 64),
430 };
431 }
432
433 fn baseReg(self: Sib, modrm: ModRm, prefixes: Prefixes) ?Register {
434 if (self.base == 0b101 and modrm.mod == 0) {
435 if (self.scaleIndex(prefixes.rex)) |_| return null;
436 return segmentRegister(prefixes.legacy);
437 }
438 return parseGpRegister(self.base, prefixes.rex.b, prefixes.rex, 64);
439 }
440};
441
442fn parseSibByte(dis: *Disassembler) !Sib {
443 if (dis.code[dis.pos..].len == 0) return error.EndOfStream;
444 const sib_byte = dis.code[dis.pos];
445 dis.pos += 1;
446 const scale: u2 = @as(u2, @truncate(sib_byte >> 6));
447 const index: u3 = @as(u3, @truncate(sib_byte >> 3));
448 const base: u3 = @as(u3, @truncate(sib_byte));
449 return Sib{ .scale = scale, .index = index, .base = base };
450}
451
452fn parseDisplacement(dis: *Disassembler, modrm: ModRm, sib: ?Sib) !i32 {
453 var stream = std.io.fixedBufferStream(dis.code[dis.pos..]);
454 var creader = std.io.countingReader(stream.reader());
455 const reader = creader.reader();
456 const disp = disp: {
457 if (sib) |info| {
458 if (info.base == 0b101 and modrm.mod == 0) {
459 break :disp try reader.readInt(i32, .Little);
460 }
461 }
462 if (modrm.rip()) {
463 break :disp try reader.readInt(i32, .Little);
464 }
465 break :disp switch (modrm.mod) {
466 0b00 => 0,
467 0b01 => try reader.readInt(i8, .Little),
468 0b10 => try reader.readInt(i32, .Little),
469 0b11 => unreachable,
470 };
471 };
472 dis.pos += creader.bytes_read;
473 return disp;
474}