1173 lines
28 KiB
Go
1173 lines
28 KiB
Go
package disasm
|
|
|
|
import "fmt"
|
|
|
|
// x86_decode.go is a from-scratch x86 / x86-64 instruction decoder. Unlike
|
|
// the previous heuristic, it tracks legacy prefixes, the REX prefix, and
|
|
// fully decodes ModR/M + SIB + displacement + immediate operands so that
|
|
// instruction *lengths* are correct (which keeps a linear sweep in sync) and
|
|
// operands are rendered in Intel syntax.
|
|
//
|
|
// Coverage: the complete general-purpose integer ISA (ALU groups, MOV/LEA,
|
|
// shifts/rotates, the F6/F7/FE/FF groups, string ops, stack ops, all near
|
|
// branches), the two-byte 0F map (Jcc/SETcc/CMOVcc, MOVZX/MOVSX, IMUL, bit
|
|
// tests, CMPXCHG/XADD, BSWAP, SHLD/SHRD, system ops) and the common SSE/SSE2
|
|
// moves and arithmetic. VEX/EVEX (AVX), x87 detail and a few rare encodings
|
|
// fall back to a one-byte `db` resync, exactly like objdump's "(bad)".
|
|
|
|
type x86dec struct {
|
|
buf []byte
|
|
pos int
|
|
base int64
|
|
is64 bool
|
|
|
|
opSize int // 16/32/64 effective operand size
|
|
addrSize int // 16/32/64 effective address size
|
|
has66 bool
|
|
seg string
|
|
rep string // "rep" / "repne" / ""
|
|
lock bool
|
|
|
|
rexW, rexR, rexX, rexB bool
|
|
hasRex bool
|
|
|
|
trunc bool // ran past end of buffer mid-instruction
|
|
hasRip bool // a RIP-relative memory operand was decoded
|
|
ripDisp int64 // its displacement (resolved once length is known)
|
|
}
|
|
|
|
func decodeX86(buf []byte, base int64, is64 bool, maxInstr int) []map[string]any {
|
|
out := []map[string]any{}
|
|
i := 0
|
|
for i < len(buf) && len(out) < maxInstr {
|
|
d := &x86dec{buf: buf, pos: i, base: base, is64: is64}
|
|
mn, ops, conf := d.decode()
|
|
var length int
|
|
if d.trunc || mn == "" {
|
|
length = 1
|
|
mn = "db"
|
|
ops = fmt.Sprintf("0x%02x", buf[i])
|
|
conf = false
|
|
} else {
|
|
length = d.pos - i
|
|
if length <= 0 {
|
|
length = 1
|
|
}
|
|
if i+length > len(buf) {
|
|
length = len(buf) - i
|
|
}
|
|
if d.hasRip {
|
|
abs := base + int64(i+length) + d.ripDisp
|
|
ops += fmt.Sprintf(" ; 0x%x", abs)
|
|
}
|
|
}
|
|
out = append(out, map[string]any{
|
|
"offset": base + int64(i),
|
|
"bytes": hexStr(buf[i : i+length]),
|
|
"mnemonic": mn,
|
|
"operands": ops,
|
|
"confident": conf,
|
|
})
|
|
i += length
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ---- byte readers (set trunc on overrun, never panic) ----
|
|
|
|
func (d *x86dec) u8() uint8 {
|
|
if d.pos >= len(d.buf) {
|
|
d.trunc = true
|
|
return 0
|
|
}
|
|
b := d.buf[d.pos]
|
|
d.pos++
|
|
return b
|
|
}
|
|
|
|
func (d *x86dec) u16() uint16 { return uint16(d.u8()) | uint16(d.u8())<<8 }
|
|
func (d *x86dec) u32() uint32 {
|
|
return uint32(d.u8()) | uint32(d.u8())<<8 | uint32(d.u8())<<16 | uint32(d.u8())<<24
|
|
}
|
|
func (d *x86dec) u64() uint64 { return uint64(d.u32()) | uint64(d.u32())<<32 }
|
|
|
|
func (d *x86dec) peek() (uint8, bool) {
|
|
if d.pos >= len(d.buf) {
|
|
return 0, false
|
|
}
|
|
return d.buf[d.pos], true
|
|
}
|
|
|
|
// ---- prefix + opcode dispatch ----
|
|
|
|
func (d *x86dec) decode() (string, string, bool) {
|
|
d.opSize = 32
|
|
if d.is64 {
|
|
d.addrSize = 64
|
|
} else {
|
|
d.addrSize = 32
|
|
}
|
|
prefixes:
|
|
for {
|
|
b, ok := d.peek()
|
|
if !ok {
|
|
return "", "", false
|
|
}
|
|
switch b {
|
|
case 0x66:
|
|
d.has66 = true
|
|
d.opSize = 16
|
|
d.pos++
|
|
continue
|
|
case 0x67:
|
|
if d.is64 {
|
|
d.addrSize = 32
|
|
} else {
|
|
d.addrSize = 16
|
|
}
|
|
d.pos++
|
|
continue
|
|
case 0xF0:
|
|
d.lock = true
|
|
d.pos++
|
|
continue
|
|
case 0xF2:
|
|
d.rep = "repne"
|
|
d.pos++
|
|
continue
|
|
case 0xF3:
|
|
d.rep = "rep"
|
|
d.pos++
|
|
continue
|
|
case 0x2E:
|
|
d.seg = "cs"
|
|
d.pos++
|
|
continue
|
|
case 0x36:
|
|
d.seg = "ss"
|
|
d.pos++
|
|
continue
|
|
case 0x3E:
|
|
d.seg = "ds"
|
|
d.pos++
|
|
continue
|
|
case 0x26:
|
|
d.seg = "es"
|
|
d.pos++
|
|
continue
|
|
case 0x64:
|
|
d.seg = "fs"
|
|
d.pos++
|
|
continue
|
|
case 0x65:
|
|
d.seg = "gs"
|
|
d.pos++
|
|
continue
|
|
}
|
|
if d.is64 && b >= 0x40 && b <= 0x4f {
|
|
d.hasRex = true
|
|
d.rexW = b&0x08 != 0
|
|
d.rexR = b&0x04 != 0
|
|
d.rexX = b&0x02 != 0
|
|
d.rexB = b&0x01 != 0
|
|
d.pos++
|
|
}
|
|
break prefixes
|
|
}
|
|
if d.rexW {
|
|
d.opSize = 64
|
|
}
|
|
|
|
op := d.u8()
|
|
if d.trunc {
|
|
return "", "", false
|
|
}
|
|
if op == 0x0f {
|
|
return d.decode0F()
|
|
}
|
|
return d.decode1(op)
|
|
}
|
|
|
|
// ---- register naming ----
|
|
|
|
var reg8 = [...]string{"al", "cl", "dl", "bl", "ah", "ch", "dh", "bh"}
|
|
var reg8rex = [...]string{"al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil",
|
|
"r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b"}
|
|
var reg16 = [...]string{"ax", "cx", "dx", "bx", "sp", "bp", "si", "di",
|
|
"r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w"}
|
|
var reg32 = [...]string{"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi",
|
|
"r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d"}
|
|
var reg64 = [...]string{"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
|
|
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"}
|
|
|
|
func (d *x86dec) regName(size, n int) string {
|
|
switch size {
|
|
case 8:
|
|
if d.hasRex {
|
|
return reg8rex[n&15]
|
|
}
|
|
return reg8[n&7]
|
|
case 16:
|
|
return reg16[n&15]
|
|
case 64:
|
|
return reg64[n&15]
|
|
case 128:
|
|
return fmt.Sprintf("xmm%d", n&15)
|
|
default:
|
|
return reg32[n&15]
|
|
}
|
|
}
|
|
|
|
func accReg(size int) string {
|
|
switch size {
|
|
case 8:
|
|
return "al"
|
|
case 16:
|
|
return "ax"
|
|
case 64:
|
|
return "rax"
|
|
default:
|
|
return "eax"
|
|
}
|
|
}
|
|
|
|
func ptrSize(size int) string {
|
|
switch size {
|
|
case 8:
|
|
return "byte ptr"
|
|
case 16:
|
|
return "word ptr"
|
|
case 64:
|
|
return "qword ptr"
|
|
case 128:
|
|
return "xmmword ptr"
|
|
default:
|
|
return "dword ptr"
|
|
}
|
|
}
|
|
|
|
// ---- ModR/M + SIB ----
|
|
|
|
// modrm reads the ModR/M byte (and any SIB/displacement) and returns the
|
|
// extended reg field plus the textual r/m operand sized to rmSize.
|
|
func (d *x86dec) modrm(rmSize int) (int, string) {
|
|
return d.modrmEx(rmSize, true)
|
|
}
|
|
|
|
func (d *x86dec) modrmEx(rmSize int, ptr bool) (int, string) {
|
|
m := d.u8()
|
|
mod := int(m >> 6)
|
|
reg := int((m >> 3) & 7)
|
|
if d.rexR {
|
|
reg += 8
|
|
}
|
|
rm := int(m & 7)
|
|
if mod == 3 {
|
|
r := rm
|
|
if d.rexB {
|
|
r += 8
|
|
}
|
|
return reg, d.regName(rmSize, r)
|
|
}
|
|
if d.addrSize == 16 {
|
|
return reg, d.mem16(mod, rm, rmSize, ptr)
|
|
}
|
|
return reg, d.mem32(mod, rm, rmSize, ptr)
|
|
}
|
|
|
|
func (d *x86dec) mem32(mod, rm, opsize int, ptr bool) string {
|
|
var base, index string
|
|
scale := 1
|
|
dispSize := 0
|
|
ripRel := false
|
|
|
|
switch {
|
|
case rm == 4:
|
|
sib := d.u8()
|
|
scale = 1 << ((sib >> 6) & 3)
|
|
idx := int((sib >> 3) & 7)
|
|
baseRaw := int(sib & 7)
|
|
if !(idx == 4 && !d.rexX) {
|
|
ir := idx
|
|
if d.rexX {
|
|
ir += 8
|
|
}
|
|
index = d.regName(d.addrSize, ir)
|
|
}
|
|
if baseRaw == 5 && mod == 0 {
|
|
dispSize = 32
|
|
} else {
|
|
br := baseRaw
|
|
if d.rexB {
|
|
br += 8
|
|
}
|
|
base = d.regName(d.addrSize, br)
|
|
}
|
|
case rm == 5 && mod == 0:
|
|
dispSize = 32
|
|
if d.is64 {
|
|
ripRel = true
|
|
}
|
|
default:
|
|
br := rm
|
|
if d.rexB {
|
|
br += 8
|
|
}
|
|
base = d.regName(d.addrSize, br)
|
|
}
|
|
|
|
switch mod {
|
|
case 1:
|
|
if dispSize == 0 {
|
|
dispSize = 8
|
|
}
|
|
case 2:
|
|
if dispSize == 0 {
|
|
dispSize = 32
|
|
}
|
|
}
|
|
|
|
var disp int64
|
|
hasDisp := dispSize != 0
|
|
switch dispSize {
|
|
case 8:
|
|
disp = int64(int8(d.u8()))
|
|
case 32:
|
|
disp = int64(int32(d.u32()))
|
|
}
|
|
return d.formatMem(opsize, base, index, scale, disp, hasDisp, ripRel, ptr)
|
|
}
|
|
|
|
var amode16 = [...]string{"bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx"}
|
|
|
|
func (d *x86dec) mem16(mod, rm, opsize int, ptr bool) string {
|
|
base := amode16[rm]
|
|
dispSize := 0
|
|
if rm == 6 && mod == 0 {
|
|
base = ""
|
|
dispSize = 16
|
|
}
|
|
switch mod {
|
|
case 1:
|
|
dispSize = 8
|
|
case 2:
|
|
dispSize = 16
|
|
}
|
|
var disp int64
|
|
hasDisp := dispSize != 0
|
|
switch dispSize {
|
|
case 8:
|
|
disp = int64(int8(d.u8()))
|
|
case 16:
|
|
disp = int64(int16(d.u16()))
|
|
}
|
|
return d.formatMem(opsize, base, "", 1, disp, hasDisp, false, ptr)
|
|
}
|
|
|
|
func (d *x86dec) formatMem(opsize int, base, index string, scale int, disp int64, hasDisp, ripRel, ptr bool) string {
|
|
var inner string
|
|
if ripRel {
|
|
d.hasRip = true
|
|
d.ripDisp = disp
|
|
inner = "rip" + signHex(disp)
|
|
} else {
|
|
first := true
|
|
if base != "" {
|
|
inner += base
|
|
first = false
|
|
}
|
|
if index != "" {
|
|
if !first {
|
|
inner += "+"
|
|
}
|
|
inner += index
|
|
if scale != 1 {
|
|
inner += fmt.Sprintf("*%d", scale)
|
|
}
|
|
first = false
|
|
}
|
|
if hasDisp {
|
|
if first {
|
|
inner += fmt.Sprintf("0x%x", uint64(disp)&addrMask(d.addrSize))
|
|
} else {
|
|
inner += signHex(disp)
|
|
}
|
|
} else if first {
|
|
inner += "0x0"
|
|
}
|
|
}
|
|
seg := ""
|
|
if d.seg != "" {
|
|
seg = d.seg + ":"
|
|
}
|
|
if ptr {
|
|
return ptrSize(opsize) + " " + seg + "[" + inner + "]"
|
|
}
|
|
return seg + "[" + inner + "]"
|
|
}
|
|
|
|
func addrMask(size int) uint64 {
|
|
switch size {
|
|
case 16:
|
|
return 0xffff
|
|
case 32:
|
|
return 0xffffffff
|
|
default:
|
|
return ^uint64(0)
|
|
}
|
|
}
|
|
|
|
func signHex(v int64) string {
|
|
if v < 0 {
|
|
return fmt.Sprintf("-0x%x", -v)
|
|
}
|
|
return fmt.Sprintf("+0x%x", v)
|
|
}
|
|
|
|
// ---- immediates as text ----
|
|
|
|
func (d *x86dec) immZ() string {
|
|
if d.opSize == 16 {
|
|
return fmt.Sprintf("0x%x", d.u16())
|
|
}
|
|
return fmt.Sprintf("0x%x", d.u32())
|
|
}
|
|
|
|
func (d *x86dec) immB() string { return fmt.Sprintf("0x%x", d.u8()) }
|
|
func (d *x86dec) immBs() string { return fmt.Sprintf("0x%x", uint64(int64(int8(d.u8())))) }
|
|
|
|
// ---- condition codes ----
|
|
|
|
var ccName = [...]string{"o", "no", "b", "ae", "e", "ne", "be", "a",
|
|
"s", "ns", "p", "np", "l", "ge", "le", "g"}
|
|
|
|
func (d *x86dec) rel8() string {
|
|
rel := int64(int8(d.u8()))
|
|
return fmt.Sprintf("0x%x", d.base+int64(d.pos)+rel)
|
|
}
|
|
|
|
func (d *x86dec) rel32() string {
|
|
rel := int64(int32(d.u32()))
|
|
return fmt.Sprintf("0x%x", d.base+int64(d.pos)+rel)
|
|
}
|
|
|
|
// stackSize is the operand size for implicit-stack ops (push/pop/call/jmp),
|
|
// which default to 64-bit in long mode.
|
|
func (d *x86dec) stackSize() int {
|
|
if d.is64 {
|
|
if d.has66 {
|
|
return 16
|
|
}
|
|
return 64
|
|
}
|
|
return d.opSize
|
|
}
|
|
|
|
var aluName = [...]string{"add", "or", "adc", "sbb", "and", "sub", "xor", "cmp"}
|
|
var shiftName = [...]string{"rol", "ror", "rcl", "rcr", "shl", "shr", "shl", "sar"}
|
|
|
|
// ---- one-byte opcode map ----
|
|
|
|
func (d *x86dec) decode1(op uint8) (string, string, bool) {
|
|
// ALU group: 0x00-0x3F in the standard 8-wide pattern.
|
|
if op <= 0x3f && (op&7) <= 5 {
|
|
name := aluName[(op>>3)&7]
|
|
switch op & 7 {
|
|
case 0:
|
|
reg, rm := d.modrm(8)
|
|
return name, rm + ", " + d.regName(8, reg), true
|
|
case 1:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return name, rm + ", " + d.regName(d.opSize, reg), true
|
|
case 2:
|
|
reg, rm := d.modrm(8)
|
|
return name, d.regName(8, reg) + ", " + rm, true
|
|
case 3:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return name, d.regName(d.opSize, reg) + ", " + rm, true
|
|
case 4:
|
|
return name, "al, " + d.immB(), true
|
|
case 5:
|
|
return name, accReg(d.opSize) + ", " + d.immZ(), true
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case op == 0x0f:
|
|
return d.decode0F()
|
|
|
|
// INC/DEC reg (32-bit only; REX in 64-bit is handled as a prefix).
|
|
case op >= 0x40 && op <= 0x4f && !d.is64:
|
|
name := "inc"
|
|
if op >= 0x48 {
|
|
name = "dec"
|
|
}
|
|
return name, d.regName(d.opSize, int(op&7)), true
|
|
|
|
case op >= 0x50 && op <= 0x57:
|
|
r := int(op - 0x50)
|
|
if d.rexB {
|
|
r += 8
|
|
}
|
|
return "push", d.regName(d.stackSize(), r), true
|
|
case op >= 0x58 && op <= 0x5f:
|
|
r := int(op - 0x58)
|
|
if d.rexB {
|
|
r += 8
|
|
}
|
|
return "pop", d.regName(d.stackSize(), r), true
|
|
|
|
case op == 0x63: // MOVSXD (64) / ARPL (32)
|
|
if d.is64 {
|
|
reg, rm := d.modrm(32)
|
|
return "movsxd", d.regName(64, reg) + ", " + rm, true
|
|
}
|
|
reg, rm := d.modrm(16)
|
|
return "arpl", rm + ", " + d.regName(16, reg), true
|
|
|
|
case op == 0x68:
|
|
return "push", d.immZ(), true
|
|
case op == 0x6a:
|
|
return "push", d.immBs(), true
|
|
case op == 0x69:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "imul", d.regName(d.opSize, reg) + ", " + rm + ", " + d.immZ(), true
|
|
case op == 0x6b:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "imul", d.regName(d.opSize, reg) + ", " + rm + ", " + d.immBs(), true
|
|
|
|
case op >= 0x70 && op <= 0x7f:
|
|
return "j" + ccName[op-0x70], d.rel8(), true
|
|
|
|
case op == 0x80: // GRP1 Eb, ib
|
|
reg, rm := d.modrm(8)
|
|
return aluName[reg&7], rm + ", " + d.immB(), true
|
|
case op == 0x81: // GRP1 Ev, iz
|
|
reg, rm := d.modrm(d.opSize)
|
|
return aluName[reg&7], rm + ", " + d.immZ(), true
|
|
case op == 0x82: // alias of 0x80
|
|
reg, rm := d.modrm(8)
|
|
return aluName[reg&7], rm + ", " + d.immB(), true
|
|
case op == 0x83: // GRP1 Ev, ib (sign-extended)
|
|
reg, rm := d.modrm(d.opSize)
|
|
return aluName[reg&7], rm + ", " + d.immBs(), true
|
|
|
|
case op == 0x84:
|
|
reg, rm := d.modrm(8)
|
|
return "test", rm + ", " + d.regName(8, reg), true
|
|
case op == 0x85:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "test", rm + ", " + d.regName(d.opSize, reg), true
|
|
case op == 0x86:
|
|
reg, rm := d.modrm(8)
|
|
return "xchg", rm + ", " + d.regName(8, reg), true
|
|
case op == 0x87:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "xchg", rm + ", " + d.regName(d.opSize, reg), true
|
|
|
|
case op == 0x88:
|
|
reg, rm := d.modrm(8)
|
|
return "mov", rm + ", " + d.regName(8, reg), true
|
|
case op == 0x89:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "mov", rm + ", " + d.regName(d.opSize, reg), true
|
|
case op == 0x8a:
|
|
reg, rm := d.modrm(8)
|
|
return "mov", d.regName(8, reg) + ", " + rm, true
|
|
case op == 0x8b:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "mov", d.regName(d.opSize, reg) + ", " + rm, true
|
|
case op == 0x8c: // mov Ew, Sreg
|
|
reg, rm := d.modrm(16)
|
|
return "mov", rm + ", " + segReg(reg), true
|
|
case op == 0x8d: // lea
|
|
reg, rm := d.modrmEx(d.opSize, false)
|
|
return "lea", d.regName(d.opSize, reg) + ", " + rm, true
|
|
case op == 0x8e: // mov Sreg, Ew
|
|
reg, rm := d.modrm(16)
|
|
return "mov", segReg(reg) + ", " + rm, true
|
|
case op == 0x8f: // GRP1A pop Ev
|
|
_, rm := d.modrm(d.stackSize())
|
|
return "pop", rm, true
|
|
|
|
case op == 0x90:
|
|
if d.rep == "rep" {
|
|
return "pause", "", true
|
|
}
|
|
if !d.rexB {
|
|
return "nop", "", true
|
|
}
|
|
fallthrough
|
|
case op >= 0x91 && op <= 0x97:
|
|
r := int(op - 0x90)
|
|
if d.rexB {
|
|
r += 8
|
|
}
|
|
return "xchg", accReg(d.opSize) + ", " + d.regName(d.opSize, r), true
|
|
case op == 0x98:
|
|
return map[int]string{16: "cbw", 32: "cwde", 64: "cdqe"}[d.opSize], "", true
|
|
case op == 0x99:
|
|
return map[int]string{16: "cwd", 32: "cdq", 64: "cqo"}[d.opSize], "", true
|
|
case op == 0x9b:
|
|
return "fwait", "", true
|
|
case op == 0x9c:
|
|
return "pushf", "", true
|
|
case op == 0x9d:
|
|
return "popf", "", true
|
|
case op == 0x9e:
|
|
return "sahf", "", true
|
|
case op == 0x9f:
|
|
return "lahf", "", true
|
|
|
|
case op == 0xa0:
|
|
return "mov", "al, " + d.moffs(), true
|
|
case op == 0xa1:
|
|
return "mov", accReg(d.opSize) + ", " + d.moffs(), true
|
|
case op == 0xa2:
|
|
return "mov", d.moffs() + ", al", true
|
|
case op == 0xa3:
|
|
return "mov", d.moffs() + ", " + accReg(d.opSize), true
|
|
case op == 0xa4:
|
|
return d.repPfx() + "movsb", "", true
|
|
case op == 0xa5:
|
|
return d.repPfx() + "movs" + sizeSuffix(d.opSize), "", true
|
|
case op == 0xa6:
|
|
return d.repPfx() + "cmpsb", "", true
|
|
case op == 0xa7:
|
|
return d.repPfx() + "cmps" + sizeSuffix(d.opSize), "", true
|
|
case op == 0xa8:
|
|
return "test", "al, " + d.immB(), true
|
|
case op == 0xa9:
|
|
return "test", accReg(d.opSize) + ", " + d.immZ(), true
|
|
case op == 0xaa:
|
|
return d.repPfx() + "stosb", "", true
|
|
case op == 0xab:
|
|
return d.repPfx() + "stos" + sizeSuffix(d.opSize), "", true
|
|
case op == 0xac:
|
|
return d.repPfx() + "lodsb", "", true
|
|
case op == 0xad:
|
|
return d.repPfx() + "lods" + sizeSuffix(d.opSize), "", true
|
|
case op == 0xae:
|
|
return d.repPfx() + "scasb", "", true
|
|
case op == 0xaf:
|
|
return d.repPfx() + "scas" + sizeSuffix(d.opSize), "", true
|
|
|
|
case op >= 0xb0 && op <= 0xb7:
|
|
r := int(op - 0xb0)
|
|
if d.rexB {
|
|
r += 8
|
|
}
|
|
return "mov", d.regName(8, r) + ", " + d.immB(), true
|
|
case op >= 0xb8 && op <= 0xbf:
|
|
r := int(op - 0xb8)
|
|
if d.rexB {
|
|
r += 8
|
|
}
|
|
if d.opSize == 64 {
|
|
return "movabs", d.regName(64, r) + fmt.Sprintf(", 0x%x", d.u64()), true
|
|
}
|
|
return "mov", d.regName(d.opSize, r) + ", " + d.immZ(), true
|
|
|
|
case op == 0xc0: // GRP2 Eb, ib
|
|
reg, rm := d.modrm(8)
|
|
return shiftName[reg&7], rm + ", " + d.immB(), true
|
|
case op == 0xc1: // GRP2 Ev, ib
|
|
reg, rm := d.modrm(d.opSize)
|
|
return shiftName[reg&7], rm + ", " + d.immB(), true
|
|
case op == 0xc2:
|
|
return "ret", fmt.Sprintf("0x%x", d.u16()), true
|
|
case op == 0xc3:
|
|
return "ret", "", true
|
|
case op == 0xc6: // GRP11 mov Eb, ib
|
|
_, rm := d.modrm(8)
|
|
return "mov", rm + ", " + d.immB(), true
|
|
case op == 0xc7: // GRP11 mov Ev, iz
|
|
_, rm := d.modrm(d.opSize)
|
|
return "mov", rm + ", " + d.immZ(), true
|
|
case op == 0xc8:
|
|
iw := d.u16()
|
|
ib := d.u8()
|
|
return "enter", fmt.Sprintf("0x%x, 0x%x", iw, ib), true
|
|
case op == 0xc9:
|
|
return "leave", "", true
|
|
case op == 0xca:
|
|
return "retf", fmt.Sprintf("0x%x", d.u16()), true
|
|
case op == 0xcb:
|
|
return "retf", "", true
|
|
case op == 0xcc:
|
|
return "int3", "", true
|
|
case op == 0xcd:
|
|
return "int", d.immB(), true
|
|
case op == 0xce:
|
|
return "into", "", true
|
|
case op == 0xcf:
|
|
return "iret", "", true
|
|
|
|
case op == 0xd0: // GRP2 Eb, 1
|
|
reg, rm := d.modrm(8)
|
|
return shiftName[reg&7], rm + ", 1", true
|
|
case op == 0xd1: // GRP2 Ev, 1
|
|
reg, rm := d.modrm(d.opSize)
|
|
return shiftName[reg&7], rm + ", 1", true
|
|
case op == 0xd2: // GRP2 Eb, cl
|
|
reg, rm := d.modrm(8)
|
|
return shiftName[reg&7], rm + ", cl", true
|
|
case op == 0xd3: // GRP2 Ev, cl
|
|
reg, rm := d.modrm(d.opSize)
|
|
return shiftName[reg&7], rm + ", cl", true
|
|
case op == 0xd7:
|
|
return "xlat", "", true
|
|
case op >= 0xd8 && op <= 0xdf: // x87 — consume ModR/M for length, name generic
|
|
_, rm := d.modrmEx(32, true)
|
|
return "fpu", rm, false
|
|
|
|
case op == 0xe0:
|
|
return "loopne", d.rel8(), true
|
|
case op == 0xe1:
|
|
return "loope", d.rel8(), true
|
|
case op == 0xe2:
|
|
return "loop", d.rel8(), true
|
|
case op == 0xe3:
|
|
if d.addrSize == 32 {
|
|
return "jecxz", d.rel8(), true
|
|
}
|
|
return "jrcxz", d.rel8(), true
|
|
case op == 0xe4:
|
|
return "in", "al, " + d.immB(), true
|
|
case op == 0xe5:
|
|
return "in", accReg(d.opSize) + ", " + d.immB(), true
|
|
case op == 0xe6:
|
|
return "out", d.immB() + ", al", true
|
|
case op == 0xe7:
|
|
return "out", d.immB() + ", " + accReg(d.opSize), true
|
|
case op == 0xe8:
|
|
return "call", d.rel32(), true
|
|
case op == 0xe9:
|
|
return "jmp", d.rel32(), true
|
|
case op == 0xeb:
|
|
return "jmp", d.rel8(), true
|
|
case op == 0xec:
|
|
return "in", "al, dx", true
|
|
case op == 0xed:
|
|
return "in", accReg(d.opSize) + ", dx", true
|
|
case op == 0xee:
|
|
return "out", "dx, al", true
|
|
case op == 0xef:
|
|
return "out", "dx, " + accReg(d.opSize), true
|
|
|
|
case op == 0xf1:
|
|
return "int1", "", true
|
|
case op == 0xf4:
|
|
return "hlt", "", true
|
|
case op == 0xf5:
|
|
return "cmc", "", true
|
|
case op == 0xf6:
|
|
return d.grp3(8)
|
|
case op == 0xf7:
|
|
return d.grp3(d.opSize)
|
|
case op == 0xf8:
|
|
return "clc", "", true
|
|
case op == 0xf9:
|
|
return "stc", "", true
|
|
case op == 0xfa:
|
|
return "cli", "", true
|
|
case op == 0xfb:
|
|
return "sti", "", true
|
|
case op == 0xfc:
|
|
return "cld", "", true
|
|
case op == 0xfd:
|
|
return "std", "", true
|
|
case op == 0xfe: // GRP4
|
|
reg, rm := d.modrm(8)
|
|
if reg&7 == 0 {
|
|
return "inc", rm, true
|
|
}
|
|
return "dec", rm, true
|
|
case op == 0xff: // GRP5
|
|
return d.grp5()
|
|
}
|
|
return "", "", false
|
|
}
|
|
|
|
func (d *x86dec) repPfx() string {
|
|
if d.rep == "rep" {
|
|
return "rep "
|
|
}
|
|
if d.rep == "repne" {
|
|
return "repne "
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func sizeSuffix(size int) string {
|
|
switch size {
|
|
case 16:
|
|
return "w"
|
|
case 64:
|
|
return "q"
|
|
default:
|
|
return "d"
|
|
}
|
|
}
|
|
|
|
func segReg(n int) string {
|
|
names := [...]string{"es", "cs", "ss", "ds", "fs", "gs", "?", "?"}
|
|
return names[n&7]
|
|
}
|
|
|
|
func (d *x86dec) moffs() string {
|
|
var off uint64
|
|
switch d.addrSize {
|
|
case 16:
|
|
off = uint64(d.u16())
|
|
case 32:
|
|
off = uint64(d.u32())
|
|
default:
|
|
off = d.u64()
|
|
}
|
|
seg := ""
|
|
if d.seg != "" {
|
|
seg = d.seg + ":"
|
|
}
|
|
return fmt.Sprintf("%s[0x%x]", seg, off)
|
|
}
|
|
|
|
func (d *x86dec) grp3(size int) (string, string, bool) {
|
|
reg, rm := d.modrm(size)
|
|
switch reg & 7 {
|
|
case 0, 1:
|
|
if size == 8 {
|
|
return "test", rm + ", " + d.immB(), true
|
|
}
|
|
return "test", rm + ", " + d.immZ(), true
|
|
case 2:
|
|
return "not", rm, true
|
|
case 3:
|
|
return "neg", rm, true
|
|
case 4:
|
|
return "mul", rm, true
|
|
case 5:
|
|
return "imul", rm, true
|
|
case 6:
|
|
return "div", rm, true
|
|
default:
|
|
return "idiv", rm, true
|
|
}
|
|
}
|
|
|
|
func (d *x86dec) grp5() (string, string, bool) {
|
|
// Peek reg field without committing size, then decode appropriately.
|
|
m, ok := d.peek()
|
|
if !ok {
|
|
return "", "", false
|
|
}
|
|
reg := int((m >> 3) & 7)
|
|
switch reg {
|
|
case 0:
|
|
_, rm := d.modrm(d.opSize)
|
|
return "inc", rm, true
|
|
case 1:
|
|
_, rm := d.modrm(d.opSize)
|
|
return "dec", rm, true
|
|
case 2:
|
|
_, rm := d.modrm(d.stackSize())
|
|
return "call", rm, true
|
|
case 3:
|
|
_, rm := d.modrm(d.opSize)
|
|
return "callf", rm, true
|
|
case 4:
|
|
_, rm := d.modrm(d.stackSize())
|
|
return "jmp", rm, true
|
|
case 5:
|
|
_, rm := d.modrm(d.opSize)
|
|
return "jmpf", rm, true
|
|
case 6:
|
|
_, rm := d.modrm(d.stackSize())
|
|
return "push", rm, true
|
|
default:
|
|
_, rm := d.modrm(d.opSize)
|
|
return "(bad)", rm, false
|
|
}
|
|
}
|
|
|
|
// ---- two-byte 0F map ----
|
|
|
|
func (d *x86dec) decode0F() (string, string, bool) {
|
|
op := d.u8()
|
|
if d.trunc {
|
|
return "", "", false
|
|
}
|
|
switch {
|
|
case op == 0x05:
|
|
return "syscall", "", true
|
|
case op == 0x06:
|
|
return "clts", "", true
|
|
case op == 0x07:
|
|
return "sysret", "", true
|
|
case op == 0x08:
|
|
return "invd", "", true
|
|
case op == 0x09:
|
|
return "wbinvd", "", true
|
|
case op == 0x0b:
|
|
return "ud2", "", true
|
|
case op == 0x1f: // multi-byte NOP
|
|
_, rm := d.modrm(d.opSize)
|
|
return "nop", rm, true
|
|
case op == 0x1e && d.rep == "rep": // ENDBR32/ENDBR64 (f3 0f 1e fa/fb)
|
|
m := d.u8()
|
|
if m == 0xfa {
|
|
return "endbr64", "", true
|
|
}
|
|
if m == 0xfb {
|
|
return "endbr32", "", true
|
|
}
|
|
return "nop", "", true
|
|
case op >= 0x18 && op <= 0x1e: // hints / prefetch
|
|
_, rm := d.modrmEx(8, true)
|
|
return "nop", rm, true
|
|
case op == 0x31:
|
|
return "rdtsc", "", true
|
|
case op == 0x34:
|
|
return "sysenter", "", true
|
|
case op == 0x35:
|
|
return "sysexit", "", true
|
|
case op == 0xa2:
|
|
return "cpuid", "", true
|
|
case op == 0x77:
|
|
return "emms", "", true
|
|
case op == 0xa0:
|
|
return "push", "fs", true
|
|
case op == 0xa1:
|
|
return "pop", "fs", true
|
|
case op == 0xa8:
|
|
return "push", "gs", true
|
|
case op == 0xa9:
|
|
return "pop", "gs", true
|
|
|
|
case op >= 0x40 && op <= 0x4f: // CMOVcc
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "cmov" + ccName[op-0x40], d.regName(d.opSize, reg) + ", " + rm, true
|
|
case op >= 0x80 && op <= 0x8f: // Jcc rel32
|
|
return "j" + ccName[op-0x80], d.rel32(), true
|
|
case op >= 0x90 && op <= 0x9f: // SETcc Eb
|
|
_, rm := d.modrm(8)
|
|
return "set" + ccName[op-0x90], rm, true
|
|
|
|
case op == 0xa3:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "bt", rm + ", " + d.regName(d.opSize, reg), true
|
|
case op == 0xab:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "bts", rm + ", " + d.regName(d.opSize, reg), true
|
|
case op == 0xb3:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "btr", rm + ", " + d.regName(d.opSize, reg), true
|
|
case op == 0xbb:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "btc", rm + ", " + d.regName(d.opSize, reg), true
|
|
case op == 0xba: // GRP8 Ev, ib
|
|
reg, rm := d.modrm(d.opSize)
|
|
names := map[int]string{4: "bt", 5: "bts", 6: "btr", 7: "btc"}
|
|
if n, okn := names[reg&7]; okn {
|
|
return n, rm + ", " + d.immB(), true
|
|
}
|
|
return "(bad)", rm, false
|
|
|
|
case op == 0xa4:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "shld", rm + ", " + d.regName(d.opSize, reg) + ", " + d.immB(), true
|
|
case op == 0xa5:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "shld", rm + ", " + d.regName(d.opSize, reg) + ", cl", true
|
|
case op == 0xac:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "shrd", rm + ", " + d.regName(d.opSize, reg) + ", " + d.immB(), true
|
|
case op == 0xad:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "shrd", rm + ", " + d.regName(d.opSize, reg) + ", cl", true
|
|
case op == 0xaf:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "imul", d.regName(d.opSize, reg) + ", " + rm, true
|
|
|
|
case op == 0xb0:
|
|
reg, rm := d.modrm(8)
|
|
return "cmpxchg", rm + ", " + d.regName(8, reg), true
|
|
case op == 0xb1:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "cmpxchg", rm + ", " + d.regName(d.opSize, reg), true
|
|
case op == 0xb6:
|
|
reg, rm := d.modrm(8)
|
|
return "movzx", d.regName(d.opSize, reg) + ", " + rm, true
|
|
case op == 0xb7:
|
|
reg, rm := d.modrm(16)
|
|
return "movzx", d.regName(d.opSize, reg) + ", " + rm, true
|
|
case op == 0xbe:
|
|
reg, rm := d.modrm(8)
|
|
return "movsx", d.regName(d.opSize, reg) + ", " + rm, true
|
|
case op == 0xbf:
|
|
reg, rm := d.modrm(16)
|
|
return "movsx", d.regName(d.opSize, reg) + ", " + rm, true
|
|
case op == 0xbc:
|
|
reg, rm := d.modrm(d.opSize)
|
|
name := "bsf"
|
|
if d.rep == "rep" {
|
|
name = "tzcnt"
|
|
}
|
|
return name, d.regName(d.opSize, reg) + ", " + rm, true
|
|
case op == 0xbd:
|
|
reg, rm := d.modrm(d.opSize)
|
|
name := "bsr"
|
|
if d.rep == "rep" {
|
|
name = "lzcnt"
|
|
}
|
|
return name, d.regName(d.opSize, reg) + ", " + rm, true
|
|
case op == 0xc0:
|
|
reg, rm := d.modrm(8)
|
|
return "xadd", rm + ", " + d.regName(8, reg), true
|
|
case op == 0xc1:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return "xadd", rm + ", " + d.regName(d.opSize, reg), true
|
|
case op >= 0xc8 && op <= 0xcf:
|
|
r := int(op - 0xc8)
|
|
if d.rexB {
|
|
r += 8
|
|
}
|
|
return "bswap", d.regName(d.opSize, r), true
|
|
}
|
|
// Common SSE / SSE2 (XMM) subset.
|
|
if mn, ops, ok := d.sse0F(op); ok {
|
|
return mn, ops, true
|
|
}
|
|
return "", "", false
|
|
}
|
|
|
|
// sse0F decodes the common SSE/SSE2 instructions. Mnemonics depend on the
|
|
// mandatory prefix (none / 0x66 / 0xF3 / 0xF2).
|
|
func (d *x86dec) sse0F(op uint8) (string, string, bool) {
|
|
pfx := "none"
|
|
switch {
|
|
case d.has66:
|
|
pfx = "66"
|
|
case d.rep == "rep":
|
|
pfx = "f3"
|
|
case d.rep == "repne":
|
|
pfx = "f2"
|
|
}
|
|
|
|
xmmRM := func(name string, store bool) (string, string, bool) {
|
|
reg, rm := d.modrm(128)
|
|
r := d.regName(128, reg)
|
|
if store {
|
|
return name, rm + ", " + r, true
|
|
}
|
|
return name, r + ", " + rm, true
|
|
}
|
|
|
|
switch op {
|
|
case 0x10:
|
|
return xmmRM(map[string]string{"none": "movups", "66": "movupd", "f3": "movss", "f2": "movsd"}[pfx], false)
|
|
case 0x11:
|
|
return xmmRM(map[string]string{"none": "movups", "66": "movupd", "f3": "movss", "f2": "movsd"}[pfx], true)
|
|
case 0x28:
|
|
return xmmRM(map[string]string{"none": "movaps", "66": "movapd"}[pfx2(pfx)], false)
|
|
case 0x29:
|
|
return xmmRM(map[string]string{"none": "movaps", "66": "movapd"}[pfx2(pfx)], true)
|
|
case 0x2e:
|
|
return xmmRM(map[string]string{"none": "ucomiss", "66": "ucomisd"}[pfx2(pfx)], false)
|
|
case 0x2f:
|
|
return xmmRM(map[string]string{"none": "comiss", "66": "comisd"}[pfx2(pfx)], false)
|
|
case 0x51:
|
|
return xmmRM(sseScalar("sqrt", pfx), false)
|
|
case 0x54:
|
|
return xmmRM(map[string]string{"none": "andps", "66": "andpd"}[pfx2(pfx)], false)
|
|
case 0x57:
|
|
return xmmRM(map[string]string{"none": "xorps", "66": "xorpd"}[pfx2(pfx)], false)
|
|
case 0x58:
|
|
return xmmRM(sseScalar("add", pfx), false)
|
|
case 0x59:
|
|
return xmmRM(sseScalar("mul", pfx), false)
|
|
case 0x5c:
|
|
return xmmRM(sseScalar("sub", pfx), false)
|
|
case 0x5e:
|
|
return xmmRM(sseScalar("div", pfx), false)
|
|
case 0x5d:
|
|
return xmmRM(sseScalar("min", pfx), false)
|
|
case 0x5f:
|
|
return xmmRM(sseScalar("max", pfx), false)
|
|
case 0x6e: // movd/movq xmm, r/m
|
|
sz := 32
|
|
name := "movd"
|
|
if d.opSize == 64 {
|
|
sz, name = 64, "movq"
|
|
}
|
|
reg, rm := d.modrm(sz)
|
|
return name, d.regName(128, reg) + ", " + rm, true
|
|
case 0x7e:
|
|
if pfx == "f3" { // movq xmm, xmm/m64
|
|
return xmmRM("movq", false)
|
|
}
|
|
sz := 32
|
|
name := "movd"
|
|
if d.opSize == 64 {
|
|
sz, name = 64, "movq"
|
|
}
|
|
reg, rm := d.modrm(sz)
|
|
return name, rm + ", " + d.regName(128, reg), true
|
|
case 0x6f:
|
|
return xmmRM(map[string]string{"66": "movdqa", "f3": "movdqu"}[pfx], false)
|
|
case 0x7f:
|
|
return xmmRM(map[string]string{"66": "movdqa", "f3": "movdqu"}[pfx], true)
|
|
case 0xd6:
|
|
return xmmRM("movq", true)
|
|
case 0xef:
|
|
return xmmRM("pxor", false)
|
|
case 0x76:
|
|
return xmmRM("pcmpeqd", false)
|
|
case 0x74:
|
|
return xmmRM("pcmpeqb", false)
|
|
case 0xfc:
|
|
return xmmRM("paddb", false)
|
|
case 0xfe:
|
|
return xmmRM("paddd", false)
|
|
case 0x12:
|
|
return xmmRM(map[string]string{"none": "movlps", "66": "movlpd"}[pfx2(pfx)], false)
|
|
case 0x13:
|
|
return xmmRM(map[string]string{"none": "movlps", "66": "movlpd"}[pfx2(pfx)], true)
|
|
case 0x16:
|
|
return xmmRM(map[string]string{"none": "movhps", "66": "movhpd"}[pfx2(pfx)], false)
|
|
case 0x17:
|
|
return xmmRM(map[string]string{"none": "movhps", "66": "movhpd"}[pfx2(pfx)], true)
|
|
case 0x14:
|
|
return xmmRM(map[string]string{"none": "unpcklps", "66": "unpcklpd"}[pfx2(pfx)], false)
|
|
case 0x2a:
|
|
reg, rm := d.modrm(d.opSize)
|
|
return sseScalar("cvtsi2", pfx), d.regName(128, reg) + ", " + rm, true
|
|
case 0x2c, 0x2d:
|
|
reg, rm := d.modrm(128)
|
|
return "cvt", d.regName(d.opSize, reg) + ", " + rm, true
|
|
}
|
|
return "", "", false
|
|
}
|
|
|
|
// pfx2 collapses f3/f2 to "none" for instructions that only vary between the
|
|
// no-prefix and 0x66 forms.
|
|
func pfx2(p string) string {
|
|
if p == "66" {
|
|
return "66"
|
|
}
|
|
return "none"
|
|
}
|
|
|
|
func sseScalar(base, pfx string) string {
|
|
switch pfx {
|
|
case "f3":
|
|
return base + "ss"
|
|
case "f2":
|
|
return base + "sd"
|
|
case "66":
|
|
return base + "pd"
|
|
default:
|
|
return base + "ps"
|
|
}
|
|
}
|