You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bddisasm/bddisasm/bdx86_formatter.c

1110 lines
36 KiB

/*
* Copyright (c) 2020 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
#include "include/bddisasm_crt.h"
#include "../inc/bddisasm.h"
#ifndef BDDISASM_NO_FORMAT
static const char *gReg8Bit[] =
{
"al", "cl", "dl", "bl", "ah", "ch", "dh", "bh",
};
static const char *gReg8Bit64[] =
{
"al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil",
"r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b",
"r16b", "r17b", "r18b", "r19b", "r20b", "r21b", "r22b", "r23b",
"r24b", "r25b", "r26b", "r27b", "r28b", "r29b", "r30b", "r31b",
};
static const char *gReg16Bit[] =
{
"ax", "cx", "dx", "bx", "sp", "bp", "si", "di",
"r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w",
"r16w", "r17w", "r18w", "r19w", "r20w", "r21w", "r22w", "r23w",
"r24w", "r25w", "r26w", "r27w", "r28w", "r29w", "r30w", "r31w",
};
static const char *gReg32Bit[] =
{
"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi",
"r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d",
"r16d", "r17d", "r18d", "r19d", "r20d", "r21d", "r22d", "r23d",
"r24d", "r25d", "r26d", "r27d", "r28d", "r29d", "r30d", "r31d",
};
static const char *gReg64Bit[] =
{
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
"r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
"r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31",
};
static const char *gRegFpu[] =
{
"st0", "st1", "st2", "st3", "st4", "st5", "st6", "st7",
};
static const char *gRegMmx[] =
{
"mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7",
};
static const char *gRegControl[] =
{
"cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7",
"cr8", "cr9", "cr10", "cr11", "cr12", "cr13", "cr14", "cr15",
"cr16", "cr17", "cr18", "cr19", "cr20", "cr21", "cr22", "cr23",
"cr24", "cr25", "cr26", "cr27", "cr28", "cr29", "cr30", "cr31",
};
static const char *gRegDebug[] =
{
"dr0", "dr1", "dr2", "dr3", "dr4", "dr5", "dr6", "dr7",
"dr8", "dr9", "dr10", "dr11", "dr12", "dr13", "dr14", "dr15",
"dr16", "dr17", "dr18", "dr19", "dr20", "dr21", "dr22", "dr23",
"dr24", "dr25", "dr26", "dr27", "dr28", "dr29", "dr30", "dr31",
};
static const char *gRegTest[] =
{
"tr0", "tr1", "tr2", "tr3", "tr4", "tr5", "tr6", "tr7",
"tr8", "tr9", "tr10", "tr11", "tr12", "tr13", "tr14", "tr15",
};
static const char *gRegXmm[] =
{
"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
"xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
"xmm16", "xmm17", "xmm18", "xmm19", "xmm20", "xmm21", "xmm22", "xmm23",
"xmm24", "xmm25", "xmm26", "xmm27", "xmm28", "xmm29", "xmm30", "xmm31",
};
static const char *gRegYmm[] =
{
"ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", "ymm6", "ymm7",
"ymm8", "ymm9", "ymm10", "ymm11", "ymm12", "ymm13", "ymm14", "ymm15",
"ymm16", "ymm17", "ymm18", "ymm19", "ymm20", "ymm21", "ymm22", "ymm23",
"ymm24", "ymm25", "ymm26", "ymm27", "ymm28", "ymm29", "ymm30", "ymm31"
};
static const char *gRegZmm[] =
{
"zmm0", "zmm1", "zmm2", "zmm3", "zmm4", "zmm5", "zmm6", "zmm7",
"zmm8", "zmm9", "zmm10", "zmm11", "zmm12", "zmm13", "zmm14", "zmm15",
"zmm16", "zmm17", "zmm18", "zmm19", "zmm20", "zmm21", "zmm22", "zmm23",
"zmm24", "zmm25", "zmm26", "zmm27", "zmm28", "zmm29", "zmm30", "zmm31",
};
static const char *gRegSeg[] =
{
"es", "cs", "ss", "ds", "fs", "gs", "segr6", "segr7",
};
static const char *gRegBound[] =
{
"bnd0", "bnd1", "bnd2", "bnd3",
};
static const char *gRegMask[] =
{
"k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7",
};
static const char *gRegTile[] =
{
"tmm0", "tmm1", "tmm2", "tmm3", "tmm4", "tmm5", "tmm6", "tmm7",
};
static const char *gEmbeddedRounding[] =
{
"rn", "rd", "ru", "rz",
};
//
// NdSprintf
//
static NDSTATUS
NdSprintf(
char *Destination,
ND_SIZET DestinationSize,
const char *Formatstring,
...
)
//
// Wrapper on vsnprintf.
//
{
int res;
va_list args;
if (ND_NULL == Destination)
{
return ND_STATUS_INVALID_PARAMETER;
}
if (ND_NULL == Formatstring)
{
return ND_STATUS_INVALID_PARAMETER;
}
nd_memzero(Destination, DestinationSize);
va_start(args, Formatstring);
// _vsnprintf is used instead of the more secure _vsnprintf_s because the mini-Petru supports just
// the unsecured version, and we depend on it.
res = nd_vsnprintf_s(Destination, DestinationSize, DestinationSize - 1, Formatstring, args);
va_end(args);
if ((res < 0) || ((ND_SIZET)res >= DestinationSize - 1))
{
return ND_STATUS_BUFFER_OVERFLOW;
}
return ND_STATUS_SUCCESS;
}
//
// NdToText
//
NDSTATUS
NdToText(
const INSTRUX *Instrux,
ND_UINT64 Rip,
ND_UINT32 BufferSize,
char *Buffer
)
{
NDSTATUS status;
char *res, temp[64];
ND_UINT32 opIndex, opsStored;
const ND_OPERAND *pOp;
ND_BOOL alignmentStored;
// pre-init
status = ND_STATUS_SUCCESS;
res = (char *)ND_NULL;
opIndex = 0;
opsStored = 0;
pOp = (const ND_OPERAND *)ND_NULL;
alignmentStored = ND_FALSE;
// Validate args.
if (ND_NULL == Instrux)
{
return ND_STATUS_INVALID_PARAMETER;
}
if (ND_NULL == Buffer)
{
return ND_STATUS_INVALID_PARAMETER;
}
if (BufferSize < ND_MIN_BUF_SIZE)
{
return ND_STATUS_INVALID_PARAMETER;
}
// init the text.
nd_memzero(Buffer, BufferSize);
nd_memzero(temp, sizeof(temp));
// First, store the prefixes.
// Check for REPZ/REPNZ support, and store prefixes.
if (Instrux->IsRepcEnabled)
{
if (Instrux->Rep == ND_PREFIX_G1_REPE_REPZ)
{
res = nd_strcat_s(Buffer, BufferSize, "REPZ ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
else if (Instrux->Rep == ND_PREFIX_G1_REPNE_REPNZ)
{
res = nd_strcat_s(Buffer, BufferSize, "REPNZ ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
}
// Check for REP support and store prefixes.
if (Instrux->IsRepEnabled)
{
if (Instrux->Rep == ND_PREFIX_G1_REPE_REPZ)
{
res = nd_strcat_s(Buffer, BufferSize, "REP ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
else if (Instrux->Rep == ND_PREFIX_G1_REPNE_REPNZ)
{
res = nd_strcat_s(Buffer, BufferSize, "REPNZ ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
}
if (Instrux->IsXreleaseEnabled)
{
res = nd_strcat_s(Buffer, BufferSize, "XRELEASE ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
else if (Instrux->IsXacquireEnabled)
{
res = nd_strcat_s(Buffer, BufferSize, "XACQUIRE ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
if (Instrux->IsLockEnabled)
{
res = nd_strcat_s(Buffer, BufferSize, "LOCK ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
if (Instrux->IsBndEnabled)
{
res = nd_strcat_s(Buffer, BufferSize, "BND ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
if (Instrux->IsBhintEnabled)
{
switch (Instrux->Seg)
{
case ND_PREFIX_G2_BR_TAKEN:
res = nd_strcat_s(Buffer, BufferSize, "BHT ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_PREFIX_G2_BR_NOT_TAKEN:
res = nd_strcat_s(Buffer, BufferSize, "BHNT ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_PREFIX_G2_BR_ALT:
res = nd_strcat_s(Buffer, BufferSize, "BHALT ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
default:
break;
}
}
if (Instrux->IsDntEnabled)
{
res = nd_strcat_s(Buffer, BufferSize, "DNT ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
// Store the mnemonic.
res = nd_strcat_s(Buffer, BufferSize, Instrux->Mnemonic);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
// Store NF specifier, if NoFlags presetn.
if (Instrux->HasNf)
{
res = nd_strcat_s(Buffer, BufferSize, "NF");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
// Store ZU specifier, if ZeroUpper present.
if (Instrux->HasZu)
{
res = nd_strcat_s(Buffer, BufferSize, "ZU");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
// If there are no explicit operands, we can leave.
if (0 == Instrux->ExpOperandsCount)
{
return ND_STATUS_SUCCESS;
}
// Now the operands.
for (opIndex = 0; opIndex < Instrux->OperandsCount; opIndex++)
{
status = ND_STATUS_SUCCESS;
pOp = &Instrux->Operands[opIndex];
if (pOp->Type == ND_OP_NOT_PRESENT)
{
break;
}
if (pOp->Flags.IsDefault)
{
continue;
}
// If this is a mask operand that has been used as masking for a previous operand, than we
// can safely skip it. We check this by seeing where is the operand encoded. If it's encoded
// in the evex.aaa field, than it is a conventional mask.
if ((pOp->Encoding == ND_OPE_A) && (pOp->Type == ND_OP_REG) &&
(pOp->Info.Register.Type == ND_REG_MSK) && (opIndex > 0))
{
continue;
}
// Store alignment.
if (!alignmentStored)
{
ND_SIZET idx = 0;
while ((idx < BufferSize) && (Buffer[idx]))
{
idx++;
}
while ((idx < 9) && (idx + 1 < BufferSize))
{
Buffer[idx++] = 0x20;
}
if (idx + 1 < BufferSize)
{
Buffer[idx++] = 0x20;
}
Buffer[idx] = 0;
alignmentStored = ND_TRUE;
}
// Store the comma, if this isn't the first operand.
if (opsStored > 0)
{
res = nd_strcat_s(Buffer, BufferSize, ", ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
opsStored++;
switch (pOp->Type)
{
case ND_OP_REG:
switch (pOp->Info.Register.Type)
{
case ND_REG_GPR:
{
if (pOp->Info.Register.Reg >= ND_MAX_GPR_REGS)
{
return ND_STATUS_INVALID_INSTRUX;
}
// General purpose register.
switch (pOp->Info.Register.Size)
{
case ND_SIZE_8BIT:
// 8 bit register.
if ((Instrux->EncMode != ND_ENCM_LEGACY) || Instrux->HasRex || Instrux->HasRex2)
{
res = nd_strcat_s(Buffer, BufferSize, gReg8Bit64[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
else
{
res = nd_strcat_s(Buffer, BufferSize, gReg8Bit[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_SIZE_16BIT:
// 16 bit register.
res = nd_strcat_s(Buffer, BufferSize, gReg16Bit[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_SIZE_32BIT:
// 32 bit register.
res = nd_strcat_s(Buffer, BufferSize, gReg32Bit[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_SIZE_64BIT:
// 64 bit register.
res = nd_strcat_s(Buffer, BufferSize, gReg64Bit[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
default:
return ND_STATUS_INVALID_INSTRUX;
}
}
break;
case ND_REG_SEG:
{
res = nd_strcat_s(Buffer, BufferSize, gRegSeg[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_REG_FPU:
{
if (pOp->Info.Register.Reg >= ND_MAX_FPU_REGS)
{
return ND_STATUS_INVALID_INSTRUX;
}
res = nd_strcat_s(Buffer, BufferSize, gRegFpu[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_REG_MMX:
{
if (pOp->Info.Register.Reg >= ND_MAX_MMX_REGS)
{
return ND_STATUS_INVALID_INSTRUX;
}
res = nd_strcat_s(Buffer, BufferSize, gRegMmx[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_REG_SSE:
{
if (pOp->Info.Register.Reg >= ND_MAX_SSE_REGS)
{
return ND_STATUS_INVALID_INSTRUX;
}
switch (pOp->Info.Register.Size)
{
case ND_SIZE_128BIT:
res = nd_strcat_s(Buffer, BufferSize, gRegXmm[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_SIZE_256BIT:
res = nd_strcat_s(Buffer, BufferSize, gRegYmm[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_SIZE_512BIT:
res = nd_strcat_s(Buffer, BufferSize, gRegZmm[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
default:
return ND_STATUS_INVALID_INSTRUX;
}
}
break;
case ND_REG_CR:
{
if (pOp->Info.Register.Reg >= ND_MAX_CR_REGS)
{
return ND_STATUS_INVALID_INSTRUX;
}
res = nd_strcat_s(Buffer, BufferSize, gRegControl[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_REG_DR:
{
if (pOp->Info.Register.Reg >= ND_MAX_DR_REGS)
{
return ND_STATUS_INVALID_INSTRUX;
}
res = nd_strcat_s(Buffer, BufferSize, gRegDebug[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_REG_TR:
{
if (pOp->Info.Register.Reg >= ND_MAX_TR_REGS)
{
return ND_STATUS_INVALID_INSTRUX;
}
res = nd_strcat_s(Buffer, BufferSize, gRegTest[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_REG_BND:
{
// Sanity check.
if (pOp->Info.Register.Reg >= ND_MAX_BND_REGS)
{
return ND_STATUS_INVALID_INSTRUX;
}
res = nd_strcat_s(Buffer, BufferSize, gRegBound[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_REG_MSK:
{
// Sanity check.
if (pOp->Info.Register.Reg >= ND_MAX_MSK_REGS)
{
return ND_STATUS_INVALID_INSTRUX;
}
res = nd_strcat_s(Buffer, BufferSize, gRegMask[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_REG_TILE:
{
// Sanity check.
if (pOp->Info.Register.Reg >= ND_MAX_TILE_REGS)
{
return ND_STATUS_INVALID_INSTRUX;
}
res = nd_strcat_s(Buffer, BufferSize, gRegTile[pOp->Info.Register.Reg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
default:
break;
}
if (pOp->Info.Register.Count > 1)
{
status = NdSprintf(temp, sizeof(temp), "+%d", pOp->Info.Register.Count - 1);
if (!ND_SUCCESS(status))
{
return status;
}
res = nd_strcat_s(Buffer, BufferSize, temp);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_OP_BANK:
// Nothing to show.
break;
case ND_OP_CONST:
{
// Implicit constant
status = NdSprintf(temp, sizeof(temp), "%d", pOp->Info.Constant.Const);
if (!ND_SUCCESS(status))
{
return status;
}
res = nd_strcat_s(Buffer, BufferSize, temp);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_OP_IMM:
{
switch (pOp->Size)
{
case 1:
status = NdSprintf(temp, sizeof(temp), "0x%02x", (ND_UINT8)pOp->Info.Immediate.Imm);
break;
case 2:
status = NdSprintf(temp, sizeof(temp), "0x%04x", (ND_UINT16)pOp->Info.Immediate.Imm);
break;
case 4:
status = NdSprintf(temp, sizeof(temp), "0x%08x", (ND_UINT32)pOp->Info.Immediate.Imm);
break;
case 8:
status = NdSprintf(temp, sizeof(temp), "0x%016llx", (ND_UINT64)pOp->Info.Immediate.Imm);
break;
}
if (!ND_SUCCESS(status))
{
return status;
}
res = nd_strcat_s(Buffer, BufferSize, temp);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_OP_OFFS:
{
ND_UINT64 dest = Rip + Instrux->Length + pOp->Info.RelativeOffset.Rel;
// Truncate to the actual word length.
switch (Instrux->WordLength)
{
case 2:
dest &= 0xFFFF;
break;
case 4:
dest &= 0xFFFFFFFF;
break;
default:
break;
}
status = NdSprintf(temp, sizeof(temp), "0x%llx", dest);
if (!ND_SUCCESS(status))
{
return status;
}
res = nd_strcat_s(Buffer, BufferSize, temp);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_OP_ADDR_FAR:
{
switch (Instrux->AddrLength)
{
case 4:
status = NdSprintf(temp, sizeof(temp), "0x%04x:0x%04x",
pOp->Info.Address.BaseSeg, (ND_UINT16)pOp->Info.Address.Offset);
break;
case 6:
status = NdSprintf(temp, sizeof(temp), "0x%04x:0x%08x",
pOp->Info.Address.BaseSeg, (ND_UINT32)pOp->Info.Address.Offset);
break;
case 10:
status = NdSprintf(temp, sizeof(temp), "0x%04x:0x%016llx",
pOp->Info.Address.BaseSeg, (ND_UINT64)pOp->Info.Address.Offset);
break;
default:
return ND_STATUS_INVALID_INSTRUX;
}
if (!ND_SUCCESS(status))
{
return status;
}
res = nd_strcat_s(Buffer, BufferSize, temp);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_OP_ADDR_NEAR:
{
status = NdSprintf(temp, sizeof(temp), "0x%016llx", pOp->Info.AddressNear.Target);
if (!ND_SUCCESS(status))
{
return status;
}
res = nd_strcat_s(Buffer, BufferSize, temp);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_OP_DFV:
{
ND_BOOL comma = ND_FALSE;
res = nd_strcat_s(Buffer, BufferSize, "{dfv=");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
if (pOp->Info.DefaultFlags.OF)
{
res = nd_strcat_s(Buffer, BufferSize, "OF");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
comma = ND_TRUE;
}
if (pOp->Info.DefaultFlags.SF)
{
res = nd_strcat_s(Buffer, BufferSize, comma ? ",SF" : "SF");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
comma = ND_TRUE;
}
if (pOp->Info.DefaultFlags.ZF)
{
res = nd_strcat_s(Buffer, BufferSize, comma ? ",ZF" : "ZF");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
comma = ND_TRUE;
}
if (pOp->Info.DefaultFlags.CF)
{
res = nd_strcat_s(Buffer, BufferSize, comma ? ",CF" : "CF");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
res = nd_strcat_s(Buffer, BufferSize, "}");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_OP_MEM:
{
// Prepend the size. For VSIB addressing, store the VSIB element size, not the total accessed size.
ND_OPERAND_SIZE size = pOp->Info.Memory.IsVsib ? pOp->Info.Memory.Vsib.ElemSize : pOp->Size;
switch (size)
{
case 1:
res = nd_strcat_s(Buffer, BufferSize, "byte ptr ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case 2:
res = nd_strcat_s(Buffer, BufferSize, "word ptr ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case 4:
res = nd_strcat_s(Buffer, BufferSize, "dword ptr ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case 6:
res = nd_strcat_s(Buffer, BufferSize, "fword ptr ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case 8:
res = nd_strcat_s(Buffer, BufferSize, "qword ptr ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case 10:
res = nd_strcat_s(Buffer, BufferSize, "tbyte ptr ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case 16:
res = nd_strcat_s(Buffer, BufferSize, "xmmword ptr ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case 32:
res = nd_strcat_s(Buffer, BufferSize, "ymmword ptr ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case 48:
res = nd_strcat_s(Buffer, BufferSize, "m384 ptr ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case 64:
res = nd_strcat_s(Buffer, BufferSize, "zmmword ptr ");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
default:
break;
}
// Perpend the segment, only if it is overridden via a prefix.
if (pOp->Info.Memory.HasSeg && Instrux->HasSeg)
{
if ((ND_CODE_64 != Instrux->DefCode) || (NDR_FS == pOp->Info.Memory.Seg) ||
(NDR_GS == pOp->Info.Memory.Seg))
{
res = nd_strcat_s(Buffer, BufferSize, gRegSeg[pOp->Info.Memory.Seg]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
res = nd_strcat_s(Buffer, BufferSize, ":");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
}
// Prepend the "["
res = nd_strcat_s(Buffer, BufferSize, "[");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
// Base, if any.
if (pOp->Info.Memory.HasBase)
{
switch (pOp->Info.Memory.BaseSize)
{
case ND_SIZE_8BIT:
if ((Instrux->EncMode != ND_ENCM_LEGACY) || Instrux->HasRex || Instrux->HasRex2)
{
res = nd_strcat_s(Buffer, BufferSize, gReg8Bit64[pOp->Info.Memory.Base]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
else
{
res = nd_strcat_s(Buffer, BufferSize, gReg8Bit[pOp->Info.Memory.Base]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_SIZE_16BIT:
res = nd_strcat_s(Buffer, BufferSize, gReg16Bit[pOp->Info.Memory.Base]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_SIZE_32BIT:
res = nd_strcat_s(Buffer, BufferSize, gReg32Bit[pOp->Info.Memory.Base]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_SIZE_64BIT:
res = nd_strcat_s(Buffer, BufferSize, gReg64Bit[pOp->Info.Memory.Base]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
default:
return ND_STATUS_INVALID_INSTRUX;
}
}
// Index, if any. Special treatment for VSIB addressing. Also, perpend a "+" if base is present.
if (pOp->Info.Memory.HasIndex)
{
if (pOp->Info.Memory.Index >= (pOp->Info.Memory.IsVsib ? ND_MAX_SSE_REGS : ND_MAX_GPR_REGS))
{
return ND_STATUS_INVALID_INSTRUX;
}
if (pOp->Info.Memory.HasBase)
{
res = nd_strcat_s(Buffer, BufferSize, "+");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
switch (pOp->Info.Memory.IndexSize)
{
case ND_SIZE_8BIT:
if ((Instrux->EncMode != ND_ENCM_LEGACY) || Instrux->HasRex || Instrux->HasRex2)
{
res = nd_strcat_s(Buffer, BufferSize, gReg8Bit64[pOp->Info.Memory.Index]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
else
{
res = nd_strcat_s(Buffer, BufferSize, gReg8Bit[pOp->Info.Memory.Index]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
break;
case ND_SIZE_16BIT:
res = nd_strcat_s(Buffer, BufferSize, gReg16Bit[pOp->Info.Memory.Index]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_SIZE_32BIT:
res = nd_strcat_s(Buffer, BufferSize, gReg32Bit[pOp->Info.Memory.Index]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_SIZE_64BIT:
res = nd_strcat_s(Buffer, BufferSize, gReg64Bit[pOp->Info.Memory.Index]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_SIZE_128BIT:
res = nd_strcat_s(Buffer, BufferSize, gRegXmm[pOp->Info.Memory.Index]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_SIZE_256BIT:
res = nd_strcat_s(Buffer, BufferSize, gRegYmm[pOp->Info.Memory.Index]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
case ND_SIZE_512BIT:
res = nd_strcat_s(Buffer, BufferSize, gRegZmm[pOp->Info.Memory.Index]);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
break;
default:
return ND_STATUS_INVALID_INSTRUX;
}
// If index is present, scale is also present.
if (pOp->Info.Memory.Scale != 1 && !pOp->Info.Memory.IsMib)
{
status = NdSprintf(temp, sizeof(temp), "*%d", pOp->Info.Memory.Scale);
if (!ND_SUCCESS(status))
{
return status;
}
res = nd_strcat_s(Buffer, BufferSize, temp);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
}
// Handle displacement.
if (pOp->Info.Memory.HasDisp)
{
ND_UINT64 normDisp, disp;
disp = pOp->Info.Memory.Disp;
// If this is direct addressing (O operand) or we don't have base or index, than we don't normalize
// the displacement, since it is used as a direct offset. Note that the second condition also
// includes the RIP-relative case.
if (pOp->Info.Memory.IsDirect || !(pOp->Info.Memory.HasBase || pOp->Info.Memory.HasIndex))
{
normDisp = disp;
}
else
{
switch (pOp->Info.Memory.DispSize)
{
case 1:
normDisp = ((disp & 0x80) ? ~disp + 1ULL : disp) & 0xFF;
break;
case 2:
normDisp = ((disp & 0x8000) ? ~disp + 1ULL : disp) & 0xFFFF;
break;
case 4:
normDisp = ((disp & 0x80000000) ? ~disp + 1ULL : disp) & 0xFFFFFFFF;
break;
default:
normDisp = disp;
break;
}
// Handle compressed displacement. It is fine to cast the normDisp to ND_UINT32, as the
// compressed displacement only works with ND_UINT8 displacements. Also, in this phase,
// the normDisp is converted to a positive quantity, so no sign-extension is needed.
if (pOp->Info.Memory.HasCompDisp)
{
normDisp = (ND_UINT64)(ND_UINT32)normDisp * pOp->Info.Memory.CompDispSize;
}
}
// Now displacement.
if (pOp->Info.Memory.HasBase || pOp->Info.Memory.HasIndex)
{
ND_BOOL sign;
if (ND_GET_SIGN(8, pOp->Info.Memory.Disp))
{
sign = ND_TRUE;
}
else
{
sign = ND_FALSE;
}
res = nd_strcat_s(Buffer, BufferSize, sign ? "-" : "+");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
if (pOp->Info.Memory.IsRipRel)
{
ND_UINT64 target = disp + Rip + Instrux->Length;
if (Instrux->AddrMode == ND_ADDR_32)
{
target &= 0xFFFFFFFF;
}
status = NdSprintf(temp, sizeof(temp), "rel 0x%llx", target);
}
else
{
ND_UINT8 trimSize;
trimSize = (Instrux->AddrMode == ND_ADDR_16) ? 2 : ((Instrux->AddrMode == ND_ADDR_32) ? 4 : 8);
// Truncate the displacement size to the size of the address length.
normDisp = ND_TRIM(trimSize, normDisp);
status = NdSprintf(temp, sizeof(temp), "0x%llx", normDisp);
}
if (!ND_SUCCESS(status))
{
return status;
}
res = nd_strcat_s(Buffer, BufferSize, temp);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
// And the ending "]"
res = nd_strcat_s(Buffer, BufferSize, "]");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
// Handle memory broadcast.
if (pOp->Info.Memory.HasBroadcast)
{
status = NdSprintf(temp, sizeof(temp), "{1to%d}", pOp->Info.Memory.Broadcast.Count);
if (!ND_SUCCESS(status))
{
return status;
}
res = nd_strcat_s(Buffer, BufferSize, temp);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
}
break;
default:
return ND_STATUS_INVALID_INSTRUX;
}
// Handle masking.
if (pOp->Decorator.HasMask)
{
status = NdSprintf(temp, sizeof(temp), "{%s}", gRegMask[pOp->Decorator.Msk]);
if (!ND_SUCCESS(status))
{
return status;
}
res = nd_strcat_s(Buffer, BufferSize, temp);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
// Handle zeroing. Note that zeroing without masking is ignored.
if (pOp->Decorator.HasZero && pOp->Decorator.HasMask)
{
res = nd_strcat_s(Buffer, BufferSize, "{z}");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
// If this is the last reg/mem operand, display {sae} and {er} decorators.
if ((pOp->Type == ND_OP_MEM || pOp->Type == ND_OP_REG) &&
(opIndex + 1 >= Instrux->ExpOperandsCount || Instrux->Operands[opIndex + 1].Type == ND_OP_IMM))
{
// Append Suppress All Exceptions decorator.
if (Instrux->HasSae && !Instrux->HasEr)
{
// ER implies SAE, so if we have ER, we will list that.
res = nd_strcat_s(Buffer, BufferSize, ", {sae}");
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
// Append Embedded Rounding decorator.
if (Instrux->HasEr)
{
status = NdSprintf(temp, sizeof(temp), ", {%s-sae}", gEmbeddedRounding[Instrux->RoundingMode]);
if (!ND_SUCCESS(status))
{
return status;
}
res = nd_strcat_s(Buffer, BufferSize, temp);
RET_EQ(res, ND_NULL, ND_STATUS_BUFFER_OVERFLOW);
}
}
}
return ND_STATUS_SUCCESS;
}
#else
NDSTATUS
NdToText(
const INSTRUX *Instrux,
ND_UINT64 Rip,
ND_UINT32 BufferSize,
char *Buffer
)
{
UNREFERENCED_PARAMETER(Instrux);
UNREFERENCED_PARAMETER(Rip);
// At least make sure the buffer is ND_NULL-terminated so integrators can use NdToText without checking if the
// BDDISASM_NO_FORMAT macro is defined. This makes switching between versions with formatting and versions without
// formatting easier.
if (Buffer != ND_NULL && BufferSize >= 1)
{
*Buffer = '\0';
}
return ND_STATUS_SUCCESS;
}
#endif // !BDDISASM_NO_FORMAT