mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
- Add Fadec x86 disassembler with hover popup for FuncPtr/void Pointer nodes - Separate pointer symbol from address: // prefix, green comment coloring, independent hover/click zones (address triggers popup, symbol is passive) - Fix RVA margin and inline local offset for pointer-expanded vtable children using ptrBase field threaded through composition - Expand multi-select context menu with quick-convert, duplicate, copy address - Remove Edit Value from hex node context menu - Fix heatmap flickering on hex nodes (remove per-byte alternation) - Fix popup repositioning when moving mouse between lines - Truncate disasm popup to 6 lines with ... indicator - Add BUILD_UI_TESTS option to skip widget tests on headless CI - Add test_disasm with 35 test cases for disassembly and hex dump - Add KUSER_SHARED_DATA example .rcx file
346 lines
12 KiB
C
346 lines
12 KiB
C
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include <fadec-enc2.h>
|
|
|
|
|
|
#ifdef __GNUC__
|
|
#define LIKELY(x) __builtin_expect(!!(x), 1)
|
|
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
|
|
#if __has_attribute(cold) && __has_attribute(preserve_most)
|
|
#define HINT_COLD __attribute__((cold,preserve_most,noinline))
|
|
#elif __has_attribute(cold)
|
|
#define HINT_COLD __attribute__((cold,noinline))
|
|
#else
|
|
#define HINT_COLD
|
|
#endif
|
|
#else
|
|
#define LIKELY(x) (x)
|
|
#define UNLIKELY(x) (x)
|
|
#define HINT_COLD
|
|
#endif
|
|
|
|
#define op_reg_idx(op) (op).idx
|
|
#define op_reg_gph(op) (((op).idx & ~0x3) == 0x24)
|
|
#define op_mem_base(mem) op_reg_idx((mem).base)
|
|
#define op_mem_idx(mem) op_reg_idx((mem).idx)
|
|
|
|
static bool
|
|
op_imm_n(int64_t imm, unsigned immsz) {
|
|
if (immsz == 0 && !imm) return true;
|
|
if (immsz == 1 && (int8_t) imm == imm) return true;
|
|
if (immsz == 2 && (int16_t) imm == imm) return true;
|
|
if (immsz == 3 && (imm&0xffffff) == imm) return true;
|
|
if (immsz == 4 && (int32_t) imm == imm) return true;
|
|
if (immsz == 8 && (int64_t) imm == imm) return true;
|
|
return false;
|
|
}
|
|
|
|
HINT_COLD static unsigned
|
|
enc_seg67(uint8_t* buf, unsigned flags) {
|
|
unsigned idx = 0;
|
|
if (UNLIKELY(flags & FE_SEG_MASK)) {
|
|
unsigned seg = (0x65643e362e2600 >> (8 * (flags & FE_SEG_MASK))) & 0xff;
|
|
buf[idx++] = seg;
|
|
}
|
|
if (UNLIKELY(flags & FE_ADDR32)) buf[idx++] = 0x67;
|
|
return idx;
|
|
}
|
|
|
|
static unsigned
|
|
enc_rex_mem(FeMem op0, uint64_t op1) {
|
|
// Essentially just an and+or due to struct layout.
|
|
uint32_t val = op1 | op0.flags | (op_mem_base(op0) << 8) |
|
|
((uint32_t)op_mem_idx(op0) << 24);
|
|
// Combine REX.RXB using multiplication for branch-less code.
|
|
uint32_t masked = val & 0x08000808;
|
|
return masked ? (uint8_t) (masked * (1|(1<<15)|(1<<25)) >> 26) + 0x40 : 0;
|
|
}
|
|
|
|
static void
|
|
enc_imm(uint8_t* buf, uint64_t imm, unsigned immsz) {
|
|
#ifdef __GNUC__
|
|
// Clang doesn't fold the loop into a single store.
|
|
// See: https://github.com/llvm/llvm-project/issues/154696
|
|
if (__builtin_constant_p(immsz)) {
|
|
__builtin_memcpy(buf, &imm, immsz);
|
|
return;
|
|
}
|
|
#endif
|
|
for (unsigned i = 0; i < immsz; i++)
|
|
*buf++ = imm >> 8 * i;
|
|
}
|
|
|
|
static int
|
|
enc_mem_common(uint8_t* buf, unsigned ripoff, FeMem op0, uint64_t op1,
|
|
unsigned disp8scale) {
|
|
int mod = 0, reg = op1 & 7, rm;
|
|
unsigned sib = 0x20;
|
|
bool withsib = false;
|
|
unsigned dispsz = 0;
|
|
int32_t off = op0.off;
|
|
|
|
if (op_reg_idx(op0.idx) < 0x80) {
|
|
int scalabs = op0.scale;
|
|
if (UNLIKELY((unsigned) (op0.scale - 1) >= 8 ||
|
|
(op0.scale & (op0.scale - 1))))
|
|
return 0;
|
|
unsigned scale = (scalabs & 0xA ? 1 : 0) | (scalabs & 0xC ? 2 : 0);
|
|
sib = scale << 6 | (op_reg_idx(op0.idx) & 7) << 3;
|
|
withsib = true;
|
|
} else if (UNLIKELY(op0.scale != 0)) {
|
|
return 0;
|
|
}
|
|
|
|
if (UNLIKELY(op0.base.idx >= 0x20)) {
|
|
if (UNLIKELY(op0.base.idx >= op_reg_idx(FE_NOREG))) {
|
|
*buf++ = (reg << 3) | 4;
|
|
*buf++ = sib | 5;
|
|
enc_imm(buf, off, 4);
|
|
return ripoff + 6;
|
|
} else if (LIKELY(op0.base.idx == FE_IP.idx)) {
|
|
if (withsib)
|
|
return 0;
|
|
*buf++ = (reg << 3) | 5;
|
|
// Adjust offset, caller doesn't know instruction length.
|
|
enc_imm(buf, off - ripoff - 5, 4);
|
|
return ripoff + 5;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
rm = op_reg_idx(op0.base) & 7;
|
|
|
|
if (off) {
|
|
if (LIKELY(!disp8scale)) {
|
|
mod = (int8_t) off == off ? 0x40 : 0x80;
|
|
dispsz = (int8_t) off == off ? 1 : 4;
|
|
} else {
|
|
if (!(off & ((1 << disp8scale) - 1)) && op_imm_n(off >> disp8scale, 1))
|
|
off >>= disp8scale, mod = 0x40, dispsz = 1;
|
|
else
|
|
mod = 0x80, dispsz = 4;
|
|
}
|
|
} else if (rm == 5) {
|
|
dispsz = 1;
|
|
mod = 0x40;
|
|
}
|
|
|
|
// Always write four bytes of displacement. The buffer is always large
|
|
// enough, and we truncate by returning a smaller "written bytes" count.
|
|
if (withsib || rm == 4) {
|
|
*buf++ = mod | (reg << 3) | 4;
|
|
*buf++ = sib | rm;
|
|
enc_imm(buf, off, 4);
|
|
return ripoff + 2 + dispsz;
|
|
} else {
|
|
*buf++ = mod | (reg << 3) | rm;
|
|
enc_imm(buf, off, 4);
|
|
return ripoff + 1 + dispsz;
|
|
}
|
|
}
|
|
|
|
static int
|
|
enc_mem(uint8_t* buf, unsigned ripoff, FeMem op0, uint64_t op1, bool forcesib,
|
|
unsigned disp8scale) {
|
|
if (UNLIKELY(op_reg_idx(op0.idx) == 4))
|
|
return 0;
|
|
if (forcesib && op_reg_idx(op0.idx) == op_reg_idx(FE_NOREG)) {
|
|
op0.scale = 1;
|
|
op0.idx = FE_GP(4);
|
|
}
|
|
return enc_mem_common(buf, ripoff, op0, op1, disp8scale);
|
|
}
|
|
|
|
static int
|
|
enc_mem_vsib(uint8_t* buf, unsigned ripoff, FeMemV op0, uint64_t op1,
|
|
bool forcesib, unsigned disp8scale) {
|
|
(void) forcesib;
|
|
FeMem mem = FE_MEM(op0.base, op0.scale, FE_GP(op_reg_idx(op0.idx)), op0.off);
|
|
return enc_mem_common(buf, ripoff, mem, op1, disp8scale);
|
|
}
|
|
|
|
// EVEX/VEX "Opcode" format:
|
|
//
|
|
// | EVEX byte 4 | P P M M M - - W | Opcode byte | VEX-D VEX-D-FLIPW
|
|
// 0 8 16 24
|
|
|
|
enum {
|
|
FE_OPC_VEX_WPP_SHIFT = 8,
|
|
FE_OPC_VEX_WPP_MASK = 0x83 << FE_OPC_VEX_WPP_SHIFT,
|
|
FE_OPC_VEX_MMM_SHIFT = 10,
|
|
FE_OPC_VEX_MMM_MASK = 0x1f << FE_OPC_VEX_MMM_SHIFT,
|
|
FE_OPC_VEX_DOWNGRADE_VEX = 1 << 24,
|
|
FE_OPC_VEX_DOWNGRADE_VEX_FLIPW = 1 << 25,
|
|
};
|
|
|
|
static int
|
|
enc_vex_common(uint8_t* buf, unsigned opcode, unsigned base,
|
|
unsigned idx, unsigned reg, unsigned vvvv) {
|
|
if ((base | idx | reg | vvvv) & 0x10) return 0;
|
|
bool vex3 = ((base | idx) & 0x08) || (opcode & 0xfc00) != 0x0400;
|
|
if (vex3) {
|
|
*buf++ = 0xc4;
|
|
unsigned b1 = (opcode & FE_OPC_VEX_MMM_MASK) >> FE_OPC_VEX_MMM_SHIFT;
|
|
if (!(reg & 0x08)) b1 |= 0x80;
|
|
if (!(idx & 0x08)) b1 |= 0x40;
|
|
if (!(base & 0x08)) b1 |= 0x20;
|
|
*buf++ = b1;
|
|
unsigned b2 = (opcode & FE_OPC_VEX_WPP_MASK) >> FE_OPC_VEX_WPP_SHIFT;
|
|
if (opcode & 0x20) b2 |= 0x04;
|
|
b2 |= (vvvv ^ 0xf) << 3;
|
|
*buf++ = b2;
|
|
} else {
|
|
*buf++ = 0xc5;
|
|
unsigned b2 = opcode >> FE_OPC_VEX_WPP_SHIFT & 3;
|
|
if (opcode & 0x20) b2 |= 0x04;
|
|
if (!(reg & 0x08)) b2 |= 0x80;
|
|
b2 |= (vvvv ^ 0xf) << 3;
|
|
*buf++ = b2;
|
|
}
|
|
*buf++ = (opcode & 0xff0000) >> 16;
|
|
return 3 + vex3;
|
|
}
|
|
|
|
static int
|
|
enc_vex_reg(uint8_t* buf, unsigned opcode, uint64_t rm, uint64_t reg,
|
|
uint64_t vvvv) {
|
|
unsigned off = enc_vex_common(buf, opcode, rm, 0, reg, vvvv);
|
|
buf[off] = 0xc0 | (reg << 3 & 0x38) | (rm & 7);
|
|
return off ? off + 1 : 0;
|
|
}
|
|
|
|
static int
|
|
enc_vex_mem(uint8_t* buf, unsigned opcode, FeMem rm, uint64_t reg,
|
|
uint64_t vvvv, unsigned ripoff, bool forcesib, unsigned disp8scale) {
|
|
unsigned off = enc_vex_common(buf, opcode, op_reg_idx(rm.base), op_reg_idx(rm.idx), reg, vvvv);
|
|
unsigned memoff = enc_mem(buf + off, ripoff + off, rm, reg, forcesib, disp8scale);
|
|
return off && memoff ? memoff : 0;
|
|
}
|
|
|
|
static int
|
|
enc_vex_vsib(uint8_t* buf, unsigned opcode, FeMemV rm, uint64_t reg,
|
|
uint64_t vvvv, unsigned ripoff, bool forcesib, unsigned disp8scale) {
|
|
unsigned off = enc_vex_common(buf, opcode, op_reg_idx(rm.base), op_reg_idx(rm.idx), reg, vvvv);
|
|
unsigned memoff = enc_mem_vsib(buf + off, ripoff + off, rm, reg, forcesib, disp8scale);
|
|
return off && memoff ? memoff : 0;
|
|
}
|
|
|
|
static int
|
|
enc_evex_common(uint8_t* buf, unsigned opcode, unsigned base,
|
|
unsigned idx, unsigned reg, unsigned vvvv) {
|
|
*buf++ = 0x62;
|
|
bool evexr3 = reg & 0x08;
|
|
bool evexr4 = reg & 0x10;
|
|
bool evexb3 = base & 0x08;
|
|
bool evexb4 = base & 0x10; // evexb4 is unused in AVX-512 encoding
|
|
bool evexx3 = idx & 0x08;
|
|
bool evexx4 = idx & 0x10;
|
|
bool evexv4 = vvvv & 0x10;
|
|
unsigned b1 = (opcode & FE_OPC_VEX_MMM_MASK) >> FE_OPC_VEX_MMM_SHIFT;
|
|
if (!evexr3) b1 |= 0x80;
|
|
if (!evexx3) b1 |= 0x40;
|
|
if (!evexb3) b1 |= 0x20;
|
|
if (!evexr4) b1 |= 0x10;
|
|
if (evexb4) b1 |= 0x08;
|
|
*buf++ = b1;
|
|
unsigned b2 = (opcode & FE_OPC_VEX_WPP_MASK) >> FE_OPC_VEX_WPP_SHIFT;
|
|
if (!evexx4) b2 |= 0x04;
|
|
b2 |= (~vvvv & 0xf) << 3;
|
|
*buf++ = b2;
|
|
unsigned b3 = opcode & 0xff;
|
|
if (!evexv4) b3 |= 0x08;
|
|
*buf++ = b3;
|
|
*buf++ = (opcode & 0xff0000) >> 16;
|
|
return 5;
|
|
}
|
|
|
|
static unsigned
|
|
enc_evex_to_vex(unsigned opcode) {
|
|
return opcode & FE_OPC_VEX_DOWNGRADE_VEX_FLIPW ? opcode ^ 0x8000 : opcode;
|
|
}
|
|
|
|
// Encode AVX-512 EVEX r/m-reg, non-xmm reg, vvvv, prefer vex
|
|
static int
|
|
enc_evex_reg(uint8_t* buf, unsigned opcode, unsigned rm,
|
|
unsigned reg, unsigned vvvv) {
|
|
unsigned off;
|
|
if (!((rm | reg | vvvv) & 0x10) && (opcode & FE_OPC_VEX_DOWNGRADE_VEX))
|
|
off = enc_vex_common(buf, enc_evex_to_vex(opcode), rm, 0, reg, vvvv);
|
|
else
|
|
off = enc_evex_common(buf, opcode, rm, 0, reg, vvvv);
|
|
buf[off] = 0xc0 | (reg << 3 & 0x38) | (rm & 7);
|
|
return off + 1;
|
|
}
|
|
|
|
// Encode AVX-512 EVEX r/m-reg, xmm reg, vvvv, prefer vex
|
|
static int
|
|
enc_evex_xmm(uint8_t* buf, unsigned opcode, unsigned rm,
|
|
unsigned reg, unsigned vvvv) {
|
|
unsigned off;
|
|
if (!((rm | reg | vvvv) & 0x10) && (opcode & FE_OPC_VEX_DOWNGRADE_VEX))
|
|
off = enc_vex_common(buf, enc_evex_to_vex(opcode), rm, 0, reg, vvvv);
|
|
else
|
|
// AVX-512 XMM reg encoding uses X3 instead of B4.
|
|
off = enc_evex_common(buf, opcode, rm & 0x0f, rm >> 1, reg, vvvv);
|
|
buf[off] = 0xc0 | (reg << 3 & 0x38) | (rm & 7);
|
|
return off + 1;
|
|
}
|
|
|
|
static int
|
|
enc_evex_mem(uint8_t* buf, unsigned opcode, FeMem rm, uint64_t reg,
|
|
uint64_t vvvv, unsigned ripoff, bool forcesib, unsigned disp8scale) {
|
|
unsigned off;
|
|
if (!((op_reg_idx(rm.base) | op_reg_idx(rm.idx) | reg | vvvv) & 0x10) &&
|
|
(opcode & FE_OPC_VEX_DOWNGRADE_VEX)) {
|
|
disp8scale = 0; // Only AVX-512 EVEX compresses displacement
|
|
off = enc_vex_common(buf, enc_evex_to_vex(opcode), op_reg_idx(rm.base), op_reg_idx(rm.idx), reg, vvvv);
|
|
} else {
|
|
off = enc_evex_common(buf, opcode, op_reg_idx(rm.base), op_reg_idx(rm.idx), reg, vvvv);
|
|
}
|
|
unsigned memoff = enc_mem(buf + off, ripoff + off, rm, reg, forcesib, disp8scale);
|
|
return off && memoff ? memoff : 0;
|
|
}
|
|
|
|
static int
|
|
enc_evex_vsib(uint8_t* buf, unsigned opcode, FeMemV rm, uint64_t reg,
|
|
uint64_t vvvv, unsigned ripoff, bool forcesib, unsigned disp8scale) {
|
|
(void) vvvv;
|
|
// EVEX VSIB requires non-zero mask operand
|
|
if (!(opcode & 0x7)) return 0;
|
|
// EVEX.X4 is encoded in EVEX.V4
|
|
unsigned idx = op_reg_idx(rm.idx);
|
|
unsigned off = enc_evex_common(buf, opcode, op_reg_idx(rm.base), idx & 0x0f, reg, idx & 0x10);
|
|
unsigned memoff = enc_mem_vsib(buf + off, ripoff + off, rm, reg, forcesib, disp8scale);
|
|
return off && memoff ? memoff : 0;
|
|
}
|
|
|
|
unsigned fe64_NOP(uint8_t* buf, unsigned flags) {
|
|
unsigned len = flags ? flags : 1;
|
|
// Taken from Intel SDM
|
|
static const uint8_t tbl[] = {
|
|
0x90,
|
|
0x66, 0x90,
|
|
0x0f, 0x1f, 0x00,
|
|
0x0f, 0x1f, 0x40, 0x00,
|
|
0x0f, 0x1f, 0x44, 0x00, 0x00,
|
|
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00,
|
|
0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00,
|
|
0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
unsigned remain = len;
|
|
for (; remain > 9; remain -= 9)
|
|
for (unsigned i = 0; i < 9; i++)
|
|
*(buf++) = tbl[36 + i];
|
|
const uint8_t* src = tbl + (remain * (remain - 1)) / 2;
|
|
for (unsigned i = 0; i < remain; i++)
|
|
*(buf++) = src[i];
|
|
return len;
|
|
}
|
|
|
|
#include <fadec-encode2-private.inc>
|