// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x86asm import ( "fmt" "strings" ) type SymLookup func(uint64) (string, uint64) // GoSyntax returns the Go assembler syntax for the instruction. // The syntax was originally defined by Plan 9. // The pc is the program counter of the instruction, used for expanding // PC-relative addresses into absolute ones. // The symname function queries the symbol table for the program // being disassembled. Given a target address it returns the name and base // address of the symbol containing the target, if any; otherwise it returns "", 0. func GoSyntax(inst Inst, pc uint64, symname SymLookup) string { if symname == nil { symname = func(uint64) (string, uint64) { return "", 0 } } var args []string for i := len(inst.Args) - 1; i >= 0; i-- { a := inst.Args[i] if a == nil { continue } args = append(args, plan9Arg(&inst, pc, symname, a)) } var rep string var last Prefix for _, p := range inst.Prefix { if p == 0 || p.IsREX() || p.IsVEX() || p.IsEVEX() { break } switch { // Don't show prefixes implied by the instruction text. case p&0xFF00 == PrefixImplicit: continue // Only REP and REPN are recognized repeaters. Plan 9 syntax // treats them as separate opcodes. case p&0xFF == PrefixREP: rep = "REP; " case p&0xFF == PrefixREPN: rep = "REPNE; " default: last = p } } prefix := "" switch last & 0xFF { case 0, 0x66, 0x67: // ignore default: prefix += last.String() + " " } op := inst.Op.String() if inst.Op == VFPCLASSPD || inst.Op == VFPCLASSPS || inst.Op == VCVTTPD2DQ || inst.Op == VCVTTPD2UDQ || inst.Op == VCVTTPD2QQ || inst.Op == VCVTTPD2UQQ || inst.Op == VCVTPD2DQ || inst.Op == VCVTPD2UDQ || inst.Op == VCVTPD2QQ || inst.Op == VCVTPD2UQQ || inst.Op == VCVTPD2PS { vexL := 0 isEvex := false for i, p := range inst.Prefix { if p.IsEVEX() && i+3 < len(inst.Prefix) && inst.Prefix[i+3] != 0 { vexL = int((inst.Prefix[i+3]&0xFF)>>5) & 3 isEvex = true break } else if p.IsVEX() && i+2 < len(inst.Prefix) { if p&0xFF == 0xC4 { vexL = int((inst.Prefix[i+2]&0xFF)>>2) & 1 } else if p&0xFF == 0xC5 { vexL = int((inst.Prefix[i+1]&0xFF)>>2) & 1 } break } } if !isEvex || inst.Op == VFPCLASSPD || inst.Op == VFPCLASSPS || inst.Op == VCVTPD2DQ { switch vexL { case 0: op += "X" case 1: if inst.Op != VCMPPD && inst.Op != VCMPPS && inst.Op != VCMPPH && inst.Op != VCMPBF16 { op += "Y" } case 2: if inst.Op != VCMPPD && inst.Op != VCMPPS && inst.Op != VCMPPH && inst.Op != VCMPBF16 { op += "Z" } } } } else if inst.Op == VCVTTSD2SI || inst.Op == VCVTTSS2SI || inst.Op == VCVTSD2SI || inst.Op == VCVTSS2SI || inst.Op == VCVTSI2SD || inst.Op == VCVTSI2SS { is64 := false if (inst.Op == VCVTSI2SD || inst.Op == VCVTSI2SS) && inst.MemBytes == 8 { is64 = true } else { for _, a := range inst.Args { if r, ok := a.(Reg); ok && RAX <= r && r <= R15 { is64 = true break } } } if inst.Op == VCVTSI2SD || inst.Op == VCVTSI2SS { if is64 { op += "Q" } else { op += "L" } } else { if is64 { op += "Q" } } } else if plan9Suffix[inst.Op] { s := inst.DataSize if inst.MemBytes != 0 { s = inst.MemBytes * 8 } else if inst.Args[1] == nil { // look for register-only 64-bit instruction, like PUSHQ AX if r, ok := inst.Args[0].(Reg); ok && RAX <= r && r <= R15 { s = 64 } } switch s { case 8: op += "B" case 16: op += "W" case 32: op += "L" case 64: op += "Q" } } if inst.Broadcast { op += ".BCST" } if inst.SAE { if hasRC(inst.Op) { switch inst.Rounding { case 0: op += ".RN_SAE" case 1: op += ".RD_SAE" case 2: op += ".RU_SAE" case 3: op += ".RZ_SAE" } } else { op += ".SAE" } } if inst.Zeroing { op += ".Z" } if inst.Op == CMP { // Use reads-left-to-right ordering for comparisons. // See issue 60920. args[0], args[1] = args[1], args[0] } if args != nil { op += " " + strings.Join(args, ", ") } return rep + prefix + op } func plan9Arg(inst *Inst, pc uint64, symname func(uint64) (string, uint64), arg Arg) string { switch a := arg.(type) { case Reg: return plan9Reg[a] case Rel: if pc == 0 { break } // If the absolute address is the start of a symbol, use the name. // Otherwise use the raw address, so that things like relative // jumps show up as JMP 0x123 instead of JMP f+10(SB). // It is usually easier to search for 0x123 than to do the mental // arithmetic to find f+10. addr := pc + uint64(inst.Len) + uint64(a) if s, base := symname(addr); s != "" && addr == base { return fmt.Sprintf("%s(SB)", s) } return fmt.Sprintf("%#x", addr) case Imm: if (inst.Op == MOV || inst.Op == PUSH) && inst.DataSize == 32 { // Only try to convert an immediate to a symbol in certain // special circumstances. See issue 72942. // // On 64-bit, symbol addresses always hit the Mem case below. // Particularly, we use LEAQ to materialize the address of // a global or function. // // On 32-bit, we sometimes use MOVL. Still try to symbolize // those immediates. if s, base := symname(uint64(a)); s != "" { suffix := "" if uint64(a) != base { suffix = fmt.Sprintf("%+d", uint64(a)-base) } return fmt.Sprintf("$%s%s(SB)", s, suffix) } } if inst.Mode == 32 { return fmt.Sprintf("$%#x", uint32(a)) } if Imm(int32(a)) == a { return fmt.Sprintf("$%#x", int64(a)) } return fmt.Sprintf("$%#x", uint64(a)) case Mem: if s, disp := memArgToSymbol(a, pc, inst.Len, symname); s != "" { suffix := "" if disp != 0 { suffix = fmt.Sprintf("%+d", disp) } return fmt.Sprintf("%s%s(SB)", s, suffix) } s := "" if a.Segment != 0 { s += fmt.Sprintf("%s:", plan9Reg[a.Segment]) } if a.Disp != 0 { s += fmt.Sprintf("%#x", a.Disp) } else { s += "0" } if a.Base != 0 { s += fmt.Sprintf("(%s)", plan9Reg[a.Base]) } if a.Index != 0 && a.Scale != 0 { s += fmt.Sprintf("(%s*%d)", plan9Reg[a.Index], a.Scale) } return s } return arg.String() } func memArgToSymbol(a Mem, pc uint64, instrLen int, symname SymLookup) (string, int64) { if a.Segment != 0 || a.Disp == 0 || a.Index != 0 || a.Scale != 0 { return "", 0 } var disp uint64 switch a.Base { case IP, EIP, RIP: disp = uint64(a.Disp + int64(pc) + int64(instrLen)) case 0: disp = uint64(a.Disp) default: return "", 0 } s, base := symname(disp) return s, int64(disp) - int64(base) } var plan9Suffix = [maxOp + 1]bool{ ADC: true, ADD: true, AND: true, BSF: true, BSR: true, BT: true, BTC: true, BTR: true, BTS: true, CMP: true, CMPXCHG: true, CVTSI2SD: true, CVTSI2SS: true, CVTSD2SI: true, CVTSS2SI: true, CVTTSD2SI: true, CVTTSS2SI: true, DEC: true, DIV: true, FLDENV: true, FRSTOR: true, IDIV: true, IMUL: true, IN: true, INC: true, LEA: true, MOV: true, MOVNTI: true, MUL: true, NEG: true, NOP: true, NOT: true, OR: true, OUT: true, POP: true, POPA: true, POPCNT: true, PUSH: true, PUSHA: true, RCL: true, RCR: true, ROL: true, ROR: true, SAR: true, SBB: true, SHL: true, SHLD: true, SHR: true, SHRD: true, SUB: true, TEST: true, XADD: true, XCHG: true, XOR: true, } var plan9Reg = [...]string{ AL: "AL", CL: "CL", BL: "BL", DL: "DL", AH: "AH", CH: "CH", BH: "BH", DH: "DH", SPB: "SP", BPB: "BP", SIB: "SI", DIB: "DI", R8B: "R8", R9B: "R9", R10B: "R10", R11B: "R11", R12B: "R12", R13B: "R13", R14B: "R14", R15B: "R15", AX: "AX", CX: "CX", BX: "BX", DX: "DX", SP: "SP", BP: "BP", SI: "SI", DI: "DI", R8W: "R8", R9W: "R9", R10W: "R10", R11W: "R11", R12W: "R12", R13W: "R13", R14W: "R14", R15W: "R15", EAX: "AX", ECX: "CX", EDX: "DX", EBX: "BX", ESP: "SP", EBP: "BP", ESI: "SI", EDI: "DI", R8L: "R8", R9L: "R9", R10L: "R10", R11L: "R11", R12L: "R12", R13L: "R13", R14L: "R14", R15L: "R15", RAX: "AX", RCX: "CX", RDX: "DX", RBX: "BX", RSP: "SP", RBP: "BP", RSI: "SI", RDI: "DI", R8: "R8", R9: "R9", R10: "R10", R11: "R11", R12: "R12", R13: "R13", R14: "R14", R15: "R15", IP: "IP", EIP: "IP", RIP: "IP", F0: "F0", F1: "F1", F2: "F2", F3: "F3", F4: "F4", F5: "F5", F6: "F6", F7: "F7", M0: "M0", M1: "M1", M2: "M2", M3: "M3", M4: "M4", M5: "M5", M6: "M6", M7: "M7", X0: "X0", X1: "X1", X2: "X2", X3: "X3", X4: "X4", X5: "X5", X6: "X6", X7: "X7", X8: "X8", X9: "X9", X10: "X10", X11: "X11", X12: "X12", X13: "X13", X14: "X14", X15: "X15", X16: "X16", X17: "X17", X18: "X18", X19: "X19", X20: "X20", X21: "X21", X22: "X22", X23: "X23", X24: "X24", X25: "X25", X26: "X26", X27: "X27", X28: "X28", X29: "X29", X30: "X30", X31: "X31", Y0: "Y0", Y1: "Y1", Y2: "Y2", Y3: "Y3", Y4: "Y4", Y5: "Y5", Y6: "Y6", Y7: "Y7", Y8: "Y8", Y9: "Y9", Y10: "Y10", Y11: "Y11", Y12: "Y12", Y13: "Y13", Y14: "Y14", Y15: "Y15", Y16: "Y16", Y17: "Y17", Y18: "Y18", Y19: "Y19", Y20: "Y20", Y21: "Y21", Y22: "Y22", Y23: "Y23", Y24: "Y24", Y25: "Y25", Y26: "Y26", Y27: "Y27", Y28: "Y28", Y29: "Y29", Y30: "Y30", Y31: "Y31", Z0: "Z0", Z1: "Z1", Z2: "Z2", Z3: "Z3", Z4: "Z4", Z5: "Z5", Z6: "Z6", Z7: "Z7", Z8: "Z8", Z9: "Z9", Z10: "Z10", Z11: "Z11", Z12: "Z12", Z13: "Z13", Z14: "Z14", Z15: "Z15", Z16: "Z16", Z17: "Z17", Z18: "Z18", Z19: "Z19", Z20: "Z20", Z21: "Z21", Z22: "Z22", Z23: "Z23", Z24: "Z24", Z25: "Z25", Z26: "Z26", Z27: "Z27", Z28: "Z28", Z29: "Z29", Z30: "Z30", Z31: "Z31", K0: "K0", K1: "K1", K2: "K2", K3: "K3", K4: "K4", K5: "K5", K6: "K6", K7: "K7", CS: "CS", SS: "SS", DS: "DS", ES: "ES", FS: "FS", GS: "GS", GDTR: "GDTR", IDTR: "IDTR", LDTR: "LDTR", MSW: "MSW", TASK: "TASK", CR0: "CR0", CR1: "CR1", CR2: "CR2", CR3: "CR3", CR4: "CR4", CR5: "CR5", CR6: "CR6", CR7: "CR7", CR8: "CR8", CR9: "CR9", CR10: "CR10", CR11: "CR11", CR12: "CR12", CR13: "CR13", CR14: "CR14", CR15: "CR15", DR0: "DR0", DR1: "DR1", DR2: "DR2", DR3: "DR3", DR4: "DR4", DR5: "DR5", DR6: "DR6", DR7: "DR7", DR8: "DR8", DR9: "DR9", DR10: "DR10", DR11: "DR11", DR12: "DR12", DR13: "DR13", DR14: "DR14", DR15: "DR15", TR0: "TR0", TR1: "TR1", TR2: "TR2", TR3: "TR3", TR4: "TR4", TR5: "TR5", TR6: "TR6", TR7: "TR7", }