mirror of https://github.com/x64dbg/zydis
Improved the intel instruction formatter
Some minor bugfixes
This commit is contained in:
parent
e31a9119ff
commit
2933d1683b
|
@ -48,7 +48,7 @@ void testDecodingAndFormatting(uintptr_t baseAddress, PIMAGE_NT_HEADERS ntHeader
|
|||
reinterpret_cast<PIMAGE_SECTION_HEADER>(
|
||||
reinterpret_cast<uintptr_t>(ntHeaders) + sizeof(IMAGE_NT_HEADERS)
|
||||
+ ntHeaders->FileHeader.SizeOfOptionalHeader - sizeof(IMAGE_OPTIONAL_HEADER));
|
||||
// Decode all code sections
|
||||
// Decode and format all code sections
|
||||
for (unsigned int i = 0; i < ntHeaders->FileHeader.NumberOfSections; ++i)
|
||||
{
|
||||
if (sectionHeader->Characteristics & IMAGE_SCN_CNT_CODE)
|
||||
|
@ -60,7 +60,7 @@ void testDecodingAndFormatting(uintptr_t baseAddress, PIMAGE_NT_HEADERS ntHeader
|
|||
decoder.setInstructionPointer(baseAddress + sectionHeader->VirtualAddress);
|
||||
while (decoder.decodeInstruction(info))
|
||||
{
|
||||
|
||||
formatter.formatInstruction(info);
|
||||
}
|
||||
}
|
||||
sectionHeader++;
|
||||
|
@ -116,6 +116,5 @@ int _tmain(int argc, _TCHAR* argv[])
|
|||
<< std::endl;
|
||||
|
||||
std::cin.get();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include "VXDisassemblerTypes.h"
|
||||
#include "VXInstructionDecoder.h"
|
||||
#include "VXInstructionFormatter.h"
|
||||
#include "VXSymbolResolver.h"
|
||||
#include "VXDisassemblerUtils.h"
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/**************************************************************************************************
|
||||
|
||||
Verteron Disassembler Engine
|
||||
Version 1.0
|
||||
|
||||
Remarks : Freeware, Copyright must be included
|
||||
|
||||
Original Author : Florian Bernd
|
||||
Modifications :
|
||||
|
||||
Last change : 30. October 2014
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
|
||||
**************************************************************************************************/
|
||||
#include "VXDisassemblerUtils.h"
|
||||
|
||||
namespace Verteron
|
||||
{
|
||||
|
||||
namespace Disassembler
|
||||
{
|
||||
|
||||
uint64_t VDECalcAbsoluteTarget(const VXInstructionInfo &info, const VXOperandInfo &operand)
|
||||
{
|
||||
assert((operand.type == VXOperandType::REL_IMMEDIATE) ||
|
||||
((operand.type == VXOperandType::MEMORY) && (operand.base == VXRegister::RIP)));
|
||||
|
||||
uint64_t truncMask = 0xFFFFFFFFFFFFFFFFull;
|
||||
if (!(info.flags & IF_DISASSEMBLER_MODE_64))
|
||||
{
|
||||
truncMask >>= (64 - info.operand_mode);
|
||||
}
|
||||
uint16_t size = operand.size;
|
||||
if ((operand.type == VXOperandType::MEMORY) && (operand.base == VXRegister::RIP))
|
||||
{
|
||||
size = operand.offset;
|
||||
}
|
||||
switch (size)
|
||||
{
|
||||
case 8:
|
||||
return (info.instrPointer + operand.lval.sbyte) & truncMask;
|
||||
case 16:
|
||||
{
|
||||
uint32_t delta = operand.lval.sword & truncMask;
|
||||
if ((info.instrPointer + delta) > 0xFFFF)
|
||||
{
|
||||
return (info.instrPointer & 0xF0000) + ((info.instrPointer + delta) & 0xFFFF);
|
||||
}
|
||||
return info.instrPointer + delta;
|
||||
}
|
||||
case 32:
|
||||
return (info.instrPointer + operand.lval.sdword) & truncMask;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/**************************************************************************************************
|
||||
|
||||
Verteron Disassembler Engine
|
||||
Version 1.0
|
||||
|
||||
Remarks : Freeware, Copyright must be included
|
||||
|
||||
Original Author : Florian Bernd
|
||||
Modifications :
|
||||
|
||||
Last change : 30. October 2014
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "VXDisassemblerTypes.h"
|
||||
|
||||
namespace Verteron
|
||||
{
|
||||
|
||||
namespace Disassembler
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Calculates the absolute target address of a relative instruction operand.
|
||||
* @param info The instruction info.
|
||||
* @param operand The operand.
|
||||
* @return The absolute target address.
|
||||
*/
|
||||
uint64_t VDECalcAbsoluteTarget(const VXInstructionInfo &info, const VXOperandInfo &operand);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -475,11 +475,11 @@ uint16_t VXInstructionDecoder::getEffectiveOperandSize(const VXInstructionInfo &
|
|||
case VXDefinedOperandSize::RDQ:
|
||||
return (m_disassemblerMode == VXDisassemblerMode::M64BIT) ? 64 : 32;
|
||||
default:
|
||||
return Internal::GetSimpleOperandSize(operandSize);
|
||||
return Internal::VDEGetSimpleOperandSize(operandSize);
|
||||
}
|
||||
}
|
||||
|
||||
bool VXInstructionDecoder::decodeOperands(VXInstructionInfo& info)
|
||||
bool VXInstructionDecoder::decodeOperands(VXInstructionInfo &info)
|
||||
{
|
||||
assert(info.instrDefinition);
|
||||
// Always try to decode the first operand
|
||||
|
@ -493,7 +493,6 @@ bool VXInstructionDecoder::decodeOperands(VXInstructionInfo& info)
|
|||
{
|
||||
if (info.operand[i - 1].type != VXOperandType::NONE)
|
||||
{
|
||||
info.operand[i].access_mode = VXOperandAccessMode::READ;
|
||||
if (!decodeOperand(info, info.operand[i], info.instrDefinition->operand[i].type,
|
||||
info.instrDefinition->operand[i].size))
|
||||
{
|
||||
|
@ -502,25 +501,30 @@ bool VXInstructionDecoder::decodeOperands(VXInstructionInfo& info)
|
|||
}
|
||||
}
|
||||
// Update operand access modes
|
||||
info.operand[0].access_mode = VXOperandAccessMode::READ;
|
||||
if (info.operand[0].type != VXOperandType::NONE)
|
||||
for (unsigned int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (info.instrDefinition->flags & IDF_OPERAND1_WRITE)
|
||||
if (info.operand[i].type != VXOperandType::NONE)
|
||||
{
|
||||
info.operand[0].access_mode = VXOperandAccessMode::WRITE;
|
||||
} else if (info.instrDefinition->flags & IDF_OPERAND1_READWRITE)
|
||||
{
|
||||
info.operand[0].access_mode = VXOperandAccessMode::READWRITE;
|
||||
}
|
||||
}
|
||||
if (info.operand[1].type != VXOperandType::NONE)
|
||||
{
|
||||
if (info.instrDefinition->flags & IDF_OPERAND2_WRITE)
|
||||
{
|
||||
info.operand[1].access_mode = VXOperandAccessMode::WRITE;
|
||||
} else if (info.instrDefinition->flags & IDF_OPERAND2_READWRITE)
|
||||
{
|
||||
info.operand[1].access_mode = VXOperandAccessMode::READWRITE;
|
||||
info.operand[i].access_mode = VXOperandAccessMode::READ;
|
||||
if (i == 0)
|
||||
{
|
||||
if (info.instrDefinition->flags & IDF_OPERAND1_WRITE)
|
||||
{
|
||||
info.operand[0].access_mode = VXOperandAccessMode::WRITE;
|
||||
} else if (info.instrDefinition->flags & IDF_OPERAND1_READWRITE)
|
||||
{
|
||||
info.operand[0].access_mode = VXOperandAccessMode::READWRITE;
|
||||
}
|
||||
} else if (i == 1)
|
||||
{
|
||||
if (info.instrDefinition->flags & IDF_OPERAND2_WRITE)
|
||||
{
|
||||
info.operand[1].access_mode = VXOperandAccessMode::WRITE;
|
||||
} else if (info.instrDefinition->flags & IDF_OPERAND2_READWRITE)
|
||||
{
|
||||
info.operand[1].access_mode = VXOperandAccessMode::READWRITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@ -621,11 +625,11 @@ bool VXInstructionDecoder::decodeOperand(VXInstructionInfo &info, VXOperandInfo
|
|||
case VXDefinedOperandType::MR:
|
||||
return decodeRegisterMemoryOperand(info, operand, RegisterClass::GENERAL_PURPOSE,
|
||||
info.modrm_mod == 3 ?
|
||||
GetComplexOperandRegSize(operandSize) : GetComplexOperandMemSize(operandSize));
|
||||
VDEGetComplexOperandRegSize(operandSize) : VDEGetComplexOperandMemSize(operandSize));
|
||||
case VXDefinedOperandType::MU:
|
||||
return decodeRegisterMemoryOperand(info, operand, RegisterClass::XMM,
|
||||
info.modrm_mod == 3 ?
|
||||
GetComplexOperandRegSize(operandSize) : GetComplexOperandMemSize(operandSize));
|
||||
VDEGetComplexOperandRegSize(operandSize) : VDEGetComplexOperandMemSize(operandSize));
|
||||
case VXDefinedOperandType::N:
|
||||
// ModR/M byte may refer only to memory
|
||||
if (info.modrm_mod != 3)
|
||||
|
@ -903,24 +907,24 @@ bool VXInstructionDecoder::decodeOpcode(VXInstructionInfo &info)
|
|||
info.opcode[0] = inputCurrent();
|
||||
info.opcode_length = 1;
|
||||
// Iterate through opcode tree
|
||||
VXOpcodeTreeNode node = GetOpcodeTreeChild(GetOpcodeTreeRoot(), inputCurrent());
|
||||
VXOpcodeTreeNode node = VDEGetOpcodeTreeChild(VDEGetOpcodeTreeRoot(), inputCurrent());
|
||||
VXOpcodeTreeNodeType nodeType;
|
||||
do
|
||||
{
|
||||
uint16_t index = 0;
|
||||
nodeType = GetOpcodeNodeType(node);
|
||||
nodeType = VDEGetOpcodeNodeType(node);
|
||||
switch (nodeType)
|
||||
{
|
||||
case VXOpcodeTreeNodeType::INSTRUCTION_DEFINITION:
|
||||
{
|
||||
// Check for invalid instruction
|
||||
if (GetOpcodeNodeValue(node) == 0)
|
||||
if (VDEGetOpcodeNodeValue(node) == 0)
|
||||
{
|
||||
info.flags |= IF_ERROR_INVALID;
|
||||
return false;
|
||||
}
|
||||
// Get instruction definition
|
||||
const VXInstructionDefinition *instrDefinition = GetInstructionDefinition(node);
|
||||
const VXInstructionDefinition *instrDefinition = VDEGetInstructionDefinition(node);
|
||||
// Check for invalid 64 bit instruction
|
||||
if ((m_disassemblerMode == VXDisassemblerMode::M64BIT) &&
|
||||
(instrDefinition->flags & IDF_INVALID_64))
|
||||
|
@ -991,11 +995,11 @@ bool VXInstructionDecoder::decodeOpcode(VXInstructionInfo &info)
|
|||
{
|
||||
index = 3; // 66
|
||||
}
|
||||
if (GetOpcodeTreeChild(node, index) == 0)
|
||||
if (VDEGetOpcodeTreeChild(node, index) == 0)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
if (index && (GetOpcodeTreeChild(node, index) != 0))
|
||||
if (index && (VDEGetOpcodeTreeChild(node, index) != 0))
|
||||
{
|
||||
// Remove REP and REPNE prefix
|
||||
info.flags &= ~IF_PREFIX_REP;
|
||||
|
@ -1055,7 +1059,7 @@ bool VXInstructionDecoder::decodeOpcode(VXInstructionInfo &info)
|
|||
switch (m_preferredVendor)
|
||||
{
|
||||
case VXInstructionSetVendor::ANY:
|
||||
index = (GetOpcodeTreeChild(node, 0) != 0) ? 0 : 1;
|
||||
index = (VDEGetOpcodeTreeChild(node, 0) != 0) ? 0 : 1;
|
||||
break;
|
||||
case VXInstructionSetVendor::INTEL:
|
||||
index = 1;
|
||||
|
@ -1071,9 +1075,9 @@ bool VXInstructionDecoder::decodeOpcode(VXInstructionInfo &info)
|
|||
{
|
||||
// As all 3dnow instructions got the same operands and flag definitions, we just
|
||||
// decode a random instruction and determine the specific opcode later.
|
||||
assert(GetOpcodeTreeChild(node, 0x0C) != 0);
|
||||
assert(VDEGetOpcodeTreeChild(node, 0x0C) != 0);
|
||||
const VXInstructionDefinition *instrDefinition =
|
||||
GetInstructionDefinition(GetOpcodeTreeChild(node, 0x0C));
|
||||
VDEGetInstructionDefinition(VDEGetOpcodeTreeChild(node, 0x0C));
|
||||
// Update instruction info
|
||||
info.instrDefinition = instrDefinition;
|
||||
info.mnemonic = instrDefinition->mnemonic;
|
||||
|
@ -1094,7 +1098,7 @@ bool VXInstructionDecoder::decodeOpcode(VXInstructionInfo &info)
|
|||
}
|
||||
// Update instruction info
|
||||
instrDefinition =
|
||||
GetInstructionDefinition(GetOpcodeTreeChild(node, info.opcode[2]));
|
||||
VDEGetInstructionDefinition(VDEGetOpcodeTreeChild(node, info.opcode[2]));
|
||||
if (!instrDefinition ||
|
||||
(instrDefinition->mnemonic == VXInstructionMnemonic::INVALID))
|
||||
{
|
||||
|
@ -1179,7 +1183,7 @@ bool VXInstructionDecoder::decodeOpcode(VXInstructionInfo &info)
|
|||
default:
|
||||
assert(0);
|
||||
}
|
||||
node = GetOpcodeTreeChild(node, index);
|
||||
node = VDEGetOpcodeTreeChild(node, index);
|
||||
} while (nodeType != VXOpcodeTreeNodeType::INSTRUCTION_DEFINITION);
|
||||
return false;
|
||||
}
|
||||
|
@ -1252,6 +1256,8 @@ bool VXInstructionDecoder::decodeInstruction(VXInstructionInfo &info)
|
|||
info.mnemonic = VXInstructionMnemonic::NOP;
|
||||
info.operand[0].type = VXOperandType::NONE;
|
||||
info.operand[1].type = VXOperandType::NONE;
|
||||
info.operand[0].access_mode = VXOperandAccessMode::NA;
|
||||
info.operand[1].access_mode = VXOperandAccessMode::NA;
|
||||
}
|
||||
}
|
||||
if ((info.mnemonic == VXInstructionMnemonic::NOP) && (info.flags & IF_PREFIX_REP))
|
||||
|
@ -1279,7 +1285,7 @@ DecodeError:
|
|||
info.length = length;
|
||||
info.data[0] = firstByte;
|
||||
info.instrAddress = instrAddress;
|
||||
info.instrDefinition = Internal::GetInstructionDefinition(0);
|
||||
info.instrDefinition = Internal::VDEGetInstructionDefinition(0);
|
||||
// Return with error, if the end of the input source was reached while decoding the
|
||||
// invalid instruction
|
||||
if (info.flags & IF_ERROR_END_OF_INPUT)
|
||||
|
|
|
@ -30,7 +30,9 @@
|
|||
|
||||
**************************************************************************************************/
|
||||
#include "VXInstructionFormatter.h"
|
||||
#include "VXDisassemblerUtils.h"
|
||||
#include <cstdarg>
|
||||
#include <cctype>
|
||||
|
||||
namespace Verteron
|
||||
{
|
||||
|
@ -101,12 +103,14 @@ void VXBaseInstructionFormatter::internalFormatInstruction(VXInstructionInfo con
|
|||
|
||||
VXBaseInstructionFormatter::VXBaseInstructionFormatter()
|
||||
: m_symbolResolver(nullptr)
|
||||
, m_uppercase(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
VXBaseInstructionFormatter::VXBaseInstructionFormatter(VXBaseSymbolResolver *symbolResolver)
|
||||
: m_symbolResolver(symbolResolver)
|
||||
, m_uppercase(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -120,7 +124,7 @@ const char* VXBaseInstructionFormatter::formatInstruction(const VXInstructionInf
|
|||
if (m_outputBuffer.size() == 0)
|
||||
{
|
||||
// The basic instruction formatter only returns the instruction menmonic.
|
||||
return Internal::GetInstructionMnemonicString(info.mnemonic);
|
||||
return Internal::VDEGetInstructionMnemonicString(info.mnemonic);
|
||||
}
|
||||
// Return the formatted instruction string
|
||||
return outputString();
|
||||
|
@ -160,6 +164,14 @@ void VXBaseInstructionFormatter::outputAppend(char const *text)
|
|||
// Append the text
|
||||
m_outputBuffer.resize(offset + strLen);
|
||||
memcpy(&m_outputBuffer[offset], text, strLen);
|
||||
// Convert to uppercase
|
||||
if (m_uppercase)
|
||||
{
|
||||
for (size_t i = offset; i < m_outputBuffer.size() - 1; ++i)
|
||||
{
|
||||
m_outputBuffer[i] = toupper(m_outputBuffer[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VXBaseInstructionFormatter::outputAppendFormatted(char const *format, ...)
|
||||
|
@ -185,35 +197,27 @@ void VXBaseInstructionFormatter::outputAppendFormatted(char const *format, ...)
|
|||
// Append the formatted text
|
||||
m_outputBuffer.resize(offset + strLen);
|
||||
vsnprintf_s(&m_outputBuffer[offset], strLen, strLen, format, arguments);
|
||||
// Convert to uppercase
|
||||
if (m_uppercase)
|
||||
{
|
||||
for (size_t i = offset; i < m_outputBuffer.size() - 1; ++i)
|
||||
{
|
||||
m_outputBuffer[i] = toupper(m_outputBuffer[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
va_end(arguments);
|
||||
}
|
||||
|
||||
uint64_t VXBaseInstructionFormatter::calcAbsoluteTarget(const VXInstructionInfo &info,
|
||||
const VXOperandInfo &operand) const
|
||||
{
|
||||
switch (operand.size)
|
||||
{
|
||||
case 8:
|
||||
return (info.instrPointer + operand.lval.sbyte);
|
||||
case 16:
|
||||
return (info.instrPointer + operand.lval.sword);
|
||||
case 32:
|
||||
case 64:
|
||||
return (info.instrPointer + operand.lval.sdword);
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void VXIntelInstructionFormatter::outputAppendAddress(const VXInstructionInfo &info,
|
||||
uint64_t address)
|
||||
void VXBaseInstructionFormatter::outputAppendAddress(const VXInstructionInfo &info,
|
||||
uint64_t address, bool resolveSymbols)
|
||||
{
|
||||
uint64_t offset = 0;
|
||||
const char* name = resolveSymbol(info, address, offset);
|
||||
const char* name = nullptr;
|
||||
if (resolveSymbols)
|
||||
{
|
||||
name = resolveSymbol(info, address, offset);
|
||||
}
|
||||
if (name)
|
||||
{
|
||||
if (offset)
|
||||
|
@ -241,6 +245,154 @@ void VXIntelInstructionFormatter::outputAppendAddress(const VXInstructionInfo &i
|
|||
}
|
||||
}
|
||||
|
||||
void VXBaseInstructionFormatter::outputAppendImmediate(const VXInstructionInfo &info,
|
||||
const VXOperandInfo &operand, bool resolveSymbols)
|
||||
{
|
||||
assert(operand.type == VXOperandType::IMMEDIATE);
|
||||
uint64_t value = 0;
|
||||
if (operand.signed_lval && (operand.size != info.operand_mode))
|
||||
{
|
||||
if (operand.size == 8)
|
||||
{
|
||||
value = static_cast<int64_t>(operand.lval.sbyte);
|
||||
} else
|
||||
{
|
||||
assert(operand.size == 32);
|
||||
value = static_cast<int64_t>(operand.lval.sdword);
|
||||
}
|
||||
if (info.operand_mode < 64)
|
||||
{
|
||||
value = value & ((1ull << info.operand_mode) - 1ull);
|
||||
}
|
||||
} else
|
||||
{
|
||||
switch (operand.size)
|
||||
{
|
||||
case 8:
|
||||
value = operand.lval.ubyte;
|
||||
break;
|
||||
case 16:
|
||||
value = operand.lval.uword;
|
||||
break;
|
||||
case 32:
|
||||
value = operand.lval.udword;
|
||||
break;
|
||||
case 64:
|
||||
value = operand.lval.uqword;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
uint64_t offset = 0;
|
||||
const char* name = nullptr;
|
||||
if (resolveSymbols)
|
||||
{
|
||||
name = resolveSymbol(info, value, offset);
|
||||
}
|
||||
if (name)
|
||||
{
|
||||
if (offset)
|
||||
{
|
||||
outputAppendFormatted("%s+%.2llX", name, offset);
|
||||
} else
|
||||
{
|
||||
outputAppend(name);
|
||||
}
|
||||
} else
|
||||
{
|
||||
outputAppendFormatted("%.2llX", value);
|
||||
}
|
||||
}
|
||||
|
||||
void VXBaseInstructionFormatter::outputAppendDisplacement(const VXInstructionInfo &info,
|
||||
const VXOperandInfo &operand)
|
||||
{
|
||||
assert(operand.offset > 0);
|
||||
if (operand.base == VXRegister::NONE && operand.index == VXRegister::NONE)
|
||||
{
|
||||
// Assume the displacement value is unsigned
|
||||
assert(operand.scale == 0);
|
||||
assert(operand.offset != 8);
|
||||
uint64_t value = 0;
|
||||
switch (operand.offset)
|
||||
{
|
||||
case 16:
|
||||
value = operand.lval.uword;
|
||||
break;
|
||||
case 32:
|
||||
value = operand.lval.udword;
|
||||
break;
|
||||
case 64:
|
||||
value = operand.lval.uqword;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
outputAppendFormatted("%.2llX", value);
|
||||
} else
|
||||
{
|
||||
// The displacement value might be negative
|
||||
assert(operand.offset != 64);
|
||||
int64_t value = 0;
|
||||
switch (operand.offset)
|
||||
{
|
||||
case 8:
|
||||
value = operand.lval.sbyte;
|
||||
break;
|
||||
case 16:
|
||||
value = operand.lval.sword;
|
||||
break;
|
||||
case 32:
|
||||
value = operand.lval.sdword;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
if (value < 0)
|
||||
{
|
||||
outputAppendFormatted("-%.2lX", -value);
|
||||
} else
|
||||
{
|
||||
outputAppendFormatted("%s%.2lX", (operand.base != VXRegister::NONE ||
|
||||
operand.index != VXRegister::NONE) ? "+" : "", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void VXIntelInstructionFormatter::outputAppendOperandCast(const VXInstructionInfo &info,
|
||||
const VXOperandInfo &operand)
|
||||
{
|
||||
switch(operand.size)
|
||||
{
|
||||
case 8:
|
||||
outputAppend("byte ptr " );
|
||||
break;
|
||||
case 16:
|
||||
outputAppend("word ptr " );
|
||||
break;
|
||||
case 32:
|
||||
outputAppend("dword ptr ");
|
||||
break;
|
||||
case 64:
|
||||
outputAppend("qword ptr ");
|
||||
break;
|
||||
case 80:
|
||||
outputAppend("tword ptr ");
|
||||
break;
|
||||
case 128:
|
||||
outputAppend("oword ptr ");
|
||||
break;
|
||||
case 256:
|
||||
outputAppend("yword ptr ");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VXIntelInstructionFormatter::formatOperand(const VXInstructionInfo &info,
|
||||
const VXOperandInfo &operand)
|
||||
{
|
||||
|
@ -250,18 +402,16 @@ void VXIntelInstructionFormatter::formatOperand(const VXInstructionInfo &info,
|
|||
outputAppend(registerToString(operand.base));
|
||||
break;
|
||||
case VXOperandType::MEMORY:
|
||||
// TODO: resolve symbols for displacement only and RIP based memory operands
|
||||
if (info.flags & IF_PREFIX_SEGMENT)
|
||||
{
|
||||
outputAppendFormatted("%s:", registerToString(info.segment));
|
||||
}
|
||||
outputAppend("[");
|
||||
// TODO: Fix zero sized memory operands (lea eax, [rip+1] | lidt [0])
|
||||
//if (operand.base == VXRegister::RIP)
|
||||
//{
|
||||
// // TODO: Add option
|
||||
// outputAppendAddress(info, calcAbsoluteTarget(info, operand));
|
||||
//} else
|
||||
if (operand.base == VXRegister::RIP)
|
||||
{
|
||||
// TODO: Add option
|
||||
outputAppendAddress(info, VDECalcAbsoluteTarget(info, operand), true);
|
||||
} else
|
||||
{
|
||||
if (operand.base != VXRegister::NONE)
|
||||
{
|
||||
|
@ -276,58 +426,9 @@ void VXIntelInstructionFormatter::formatOperand(const VXInstructionInfo &info,
|
|||
outputAppendFormatted("*%d", operand.scale);
|
||||
}
|
||||
}
|
||||
if (operand.lval.uqword ||
|
||||
(operand.base == VXRegister::NONE && operand.index == VXRegister::NONE))
|
||||
if (operand.offset)
|
||||
{
|
||||
if (operand.base == VXRegister::NONE && operand.index == VXRegister::NONE)
|
||||
{
|
||||
// Assume the displacement value is unsigned
|
||||
assert(operand.scale == 0);
|
||||
assert(operand.offset != 8);
|
||||
uint64_t value = 0;
|
||||
switch (operand.offset)
|
||||
{
|
||||
case 16:
|
||||
value = operand.lval.uword;
|
||||
break;
|
||||
case 32:
|
||||
value = operand.lval.udword;
|
||||
break;
|
||||
case 64:
|
||||
value = operand.lval.uqword;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
outputAppendFormatted("%.2llX", value);
|
||||
} else
|
||||
{
|
||||
// The displacement value might be negative
|
||||
assert(operand.offset != 64);
|
||||
int64_t value = 0;
|
||||
switch (operand.offset)
|
||||
{
|
||||
case 8:
|
||||
value = operand.lval.sbyte;
|
||||
break;
|
||||
case 16:
|
||||
value = operand.lval.sword;
|
||||
break;
|
||||
case 32:
|
||||
value = operand.lval.sdword;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
if (value < 0)
|
||||
{
|
||||
outputAppendFormatted("-%.2lX", -value);
|
||||
} else
|
||||
{
|
||||
outputAppendFormatted("%s%.2lX", (operand.base != VXRegister::NONE ||
|
||||
operand.index != VXRegister::NONE) ? "+" : "", value);
|
||||
}
|
||||
}
|
||||
outputAppendDisplacement(info, operand);
|
||||
}
|
||||
}
|
||||
outputAppend("]");
|
||||
|
@ -349,26 +450,7 @@ void VXIntelInstructionFormatter::formatOperand(const VXInstructionInfo &info,
|
|||
break;
|
||||
case VXOperandType::IMMEDIATE:
|
||||
{
|
||||
// TODO: resolve symbols
|
||||
uint64_t value = 0;
|
||||
switch (operand.size)
|
||||
{
|
||||
case 8 :
|
||||
value = operand.lval.ubyte;
|
||||
break;
|
||||
case 16:
|
||||
value = operand.lval.uword;
|
||||
break;
|
||||
case 32:
|
||||
value = operand.lval.udword;
|
||||
break;
|
||||
case 64:
|
||||
value = operand.lval.uqword;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
outputAppendFormatted("%.2llX", value);
|
||||
outputAppendImmediate(info, operand, true);
|
||||
}
|
||||
break;
|
||||
case VXOperandType::REL_IMMEDIATE:
|
||||
|
@ -377,11 +459,11 @@ void VXIntelInstructionFormatter::formatOperand(const VXInstructionInfo &info,
|
|||
{
|
||||
outputAppend("short ");
|
||||
}
|
||||
outputAppendAddress(info, calcAbsoluteTarget(info, operand));
|
||||
outputAppendAddress(info, VDECalcAbsoluteTarget(info, operand), true);
|
||||
}
|
||||
break;
|
||||
case VXOperandType::CONSTANT:
|
||||
outputAppendFormatted("%d", operand.lval.udword);
|
||||
outputAppendFormatted("%.2X", operand.lval.udword);
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
|
@ -404,23 +486,82 @@ void VXIntelInstructionFormatter::internalFormatInstruction(const VXInstructionI
|
|||
outputAppend("repne ");
|
||||
}
|
||||
// Append the instruction mnemonic
|
||||
outputAppend(Internal::GetInstructionMnemonicString(info.mnemonic));
|
||||
outputAppend(Internal::VDEGetInstructionMnemonicString(info.mnemonic));
|
||||
// Append the first operand
|
||||
if (info.operand[0].type != VXOperandType::NONE)
|
||||
{
|
||||
outputAppend(" ");
|
||||
bool cast = false;
|
||||
if (info.operand[0].type == VXOperandType::MEMORY)
|
||||
{
|
||||
if (info.operand[1].type == VXOperandType::IMMEDIATE ||
|
||||
info.operand[1].type == VXOperandType::CONSTANT ||
|
||||
info.operand[1].type == VXOperandType::NONE ||
|
||||
(info.operand[0].size != info.operand[1].size))
|
||||
{
|
||||
cast = true;
|
||||
} else if (info.operand[1].type == VXOperandType::REGISTER &&
|
||||
info.operand[1].base == VXRegister::CL)
|
||||
{
|
||||
switch (info.mnemonic)
|
||||
{
|
||||
case VXInstructionMnemonic::RCL:
|
||||
case VXInstructionMnemonic::ROL:
|
||||
case VXInstructionMnemonic::ROR:
|
||||
case VXInstructionMnemonic::RCR:
|
||||
case VXInstructionMnemonic::SHL:
|
||||
case VXInstructionMnemonic::SHR:
|
||||
case VXInstructionMnemonic::SAR:
|
||||
cast = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cast)
|
||||
{
|
||||
outputAppendOperandCast(info, info.operand[0]);
|
||||
}
|
||||
formatOperand(info, info.operand[0]);
|
||||
}
|
||||
// Append the second operand
|
||||
if (info.operand[1].type != VXOperandType::NONE)
|
||||
{
|
||||
outputAppend(", ");
|
||||
bool cast = false;
|
||||
if (info.operand[1].type == VXOperandType::MEMORY &&
|
||||
info.operand[0].size != info.operand[1].size &&
|
||||
((info.operand[0].type != VXOperandType::REGISTER) ||
|
||||
((info.operand[0].base != VXRegister::ES) &&
|
||||
(info.operand[0].base != VXRegister::CS) &&
|
||||
(info.operand[0].base != VXRegister::SS) &&
|
||||
(info.operand[0].base != VXRegister::DS) &&
|
||||
(info.operand[0].base != VXRegister::FS) &&
|
||||
(info.operand[0].base != VXRegister::GS))))
|
||||
{
|
||||
cast = true;
|
||||
}
|
||||
if (cast)
|
||||
{
|
||||
outputAppendOperandCast(info, info.operand[1]);
|
||||
}
|
||||
formatOperand(info, info.operand[1]);
|
||||
}
|
||||
// Append the third operand
|
||||
if (info.operand[2].type != VXOperandType::NONE)
|
||||
{
|
||||
outputAppend(", ");
|
||||
bool cast = false;
|
||||
if (info.operand[2].type == VXOperandType::MEMORY &&
|
||||
(info.operand[2].size != info.operand[1].size))
|
||||
{
|
||||
cast = true;
|
||||
}
|
||||
if (cast)
|
||||
{
|
||||
outputAppendOperandCast(info, info.operand[2]);
|
||||
}
|
||||
formatOperand(info, info.operand[2]);
|
||||
}
|
||||
// Append the fourth operand
|
||||
|
|
|
@ -50,6 +50,7 @@ private:
|
|||
static const char *m_registerStrings[];
|
||||
VXBaseSymbolResolver *m_symbolResolver;
|
||||
std::vector<char> m_outputBuffer;
|
||||
bool m_uppercase;
|
||||
protected:
|
||||
/**
|
||||
* @brief Clears the output string buffer.
|
||||
|
@ -70,14 +71,36 @@ protected:
|
|||
* @param format The format string.
|
||||
*/
|
||||
void outputAppendFormatted(const char *format, ...);
|
||||
protected:
|
||||
/**
|
||||
* @brief Calculates the absolute target address for a relative immediate operand.
|
||||
* @param info The instruction info.
|
||||
* @param operand The operand.
|
||||
* @return The absolute target address.
|
||||
* @brief Changes automatic conversion of characters to uppercase.
|
||||
* @param uppercase Set true to enable automatic uppercase conversion.
|
||||
*/
|
||||
uint64_t calcAbsoluteTarget(const VXInstructionInfo &info, const VXOperandInfo &operand) const;
|
||||
void outputSetUppercase(bool uppercase);
|
||||
/**
|
||||
* @brief Appends a formatted address to the output string buffer.
|
||||
* @param info The instruction info.
|
||||
* @param address The address.
|
||||
* @param resolveSymbols If this parameter is true, the method will try to display a
|
||||
* smybol name instead of the numeric value.
|
||||
*/
|
||||
void outputAppendAddress(const VXInstructionInfo &info, uint64_t address,
|
||||
bool resolveSymbols = true);
|
||||
/**
|
||||
* @brief Appends a formatted immediate value to the output string buffer.
|
||||
* @param info The instruction info.
|
||||
* @param operand The immediate operand.
|
||||
* @param resolveSymbols If this parameter is true, the method will try to display a
|
||||
* smybol name instead of the numeric value.
|
||||
*/
|
||||
void outputAppendImmediate(const VXInstructionInfo &info, const VXOperandInfo &operand,
|
||||
bool resolveSymbols = false);
|
||||
/**
|
||||
* @brief Appends a formatted memory displacement value to the output string buffer.
|
||||
* @param info The instruction info.
|
||||
* @param operand The memory operand.
|
||||
*/
|
||||
void outputAppendDisplacement(const VXInstructionInfo &info, const VXOperandInfo &operand);
|
||||
protected:
|
||||
/**
|
||||
* @brief Returns the string representation of a given register.
|
||||
* @param reg The register.
|
||||
|
@ -138,6 +161,11 @@ public:
|
|||
void setSymbolResolver(VXBaseSymbolResolver *symbolResolver);
|
||||
};
|
||||
|
||||
inline void VXBaseInstructionFormatter::outputSetUppercase(bool uppercase)
|
||||
{
|
||||
m_uppercase = uppercase;
|
||||
}
|
||||
|
||||
inline char const* VXBaseInstructionFormatter::registerToString(VXRegister reg) const
|
||||
{
|
||||
if (reg == VXRegister::NONE)
|
||||
|
@ -175,8 +203,12 @@ inline void VXBaseInstructionFormatter::setSymbolResolver(VXBaseSymbolResolver *
|
|||
class VXIntelInstructionFormatter : public VXBaseInstructionFormatter
|
||||
{
|
||||
private:
|
||||
void outputAppendAddress(const VXInstructionInfo &info, uint64_t address);
|
||||
private:
|
||||
/**
|
||||
* @brief Appends an operand cast to the output string buffer.
|
||||
* @param info The instruction info.
|
||||
* @param operand The operand.
|
||||
*/
|
||||
void outputAppendOperandCast(const VXInstructionInfo &info, const VXOperandInfo &operand);
|
||||
/**
|
||||
* @brief Formats the specified operand and appends the resulting string to the output
|
||||
* buffer.
|
||||
|
|
|
@ -160,20 +160,20 @@ static const VXOpcodeTreeNode optreeTable[][256] =
|
|||
/* 6F */ NODE(VXOpcodeTreeNodeType::OPERAND_SIZE, 0x000A),
|
||||
/* 70 */ 0x02E8,
|
||||
/* 71 */ 0x02E2,
|
||||
/* 72 */ 0x02CC,
|
||||
/* 73 */ 0x02CA,
|
||||
/* 74 */ 0x02D0,
|
||||
/* 72 */ 0x02CA,
|
||||
/* 73 */ 0x02DE,
|
||||
/* 74 */ 0x02CE,
|
||||
/* 75 */ 0x02E1,
|
||||
/* 76 */ 0x02CD,
|
||||
/* 77 */ 0x02C8,
|
||||
/* 76 */ 0x02CB,
|
||||
/* 77 */ 0x02C7,
|
||||
/* 78 */ 0x02ED,
|
||||
/* 79 */ 0x02E6,
|
||||
/* 7A */ 0x02EB,
|
||||
/* 7B */ 0x02E5,
|
||||
/* 7C */ 0x02D7,
|
||||
/* 7D */ 0x02D5,
|
||||
/* 7E */ 0x02DA,
|
||||
/* 7F */ 0x02D3,
|
||||
/* 7C */ 0x02D5,
|
||||
/* 7D */ 0x02D3,
|
||||
/* 7E */ 0x02D8,
|
||||
/* 7F */ 0x02D1,
|
||||
/* 80 */ NODE(VXOpcodeTreeNodeType::MODRM_REG, 0x0013),
|
||||
/* 81 */ NODE(VXOpcodeTreeNodeType::MODRM_REG, 0x0014),
|
||||
/* 82 */ NODE(VXOpcodeTreeNodeType::MODRM_REG, 0x0015),
|
||||
|
@ -279,9 +279,9 @@ static const VXOpcodeTreeNode optreeTable[][256] =
|
|||
/* E6 */ 0x039D,
|
||||
/* E7 */ 0x039E,
|
||||
/* E8 */ 0x004E,
|
||||
/* E9 */ 0x02DD,
|
||||
/* E9 */ 0x02DB,
|
||||
/* EA */ NODE(VXOpcodeTreeNodeType::MODE, 0x002B),
|
||||
/* EB */ 0x02DE,
|
||||
/* EB */ 0x02DD,
|
||||
/* EC */ 0x02A7,
|
||||
/* ED */ 0x02A8,
|
||||
/* EE */ 0x039B,
|
||||
|
@ -3224,8 +3224,8 @@ static const VXOpcodeTreeNode optreeModrmReg[][8] =
|
|||
/* 01 */ 0x00A5,
|
||||
/* 02 */ NODE(VXOpcodeTreeNodeType::MODE, 0x002C),
|
||||
/* 03 */ 0x004C,
|
||||
/* 04 */ 0x02DC,
|
||||
/* 05 */ 0x02DB,
|
||||
/* 04 */ 0x02D9,
|
||||
/* 05 */ 0x02DA,
|
||||
/* 06 */ 0x04B8,
|
||||
/* 07 */ INVALID,
|
||||
},
|
||||
|
@ -4535,12 +4535,6 @@ static const VXOpcodeTreeNode optreeMandatory[][4] =
|
|||
/* 02 */ INVALID,
|
||||
/* 03 */ INVALID,
|
||||
},
|
||||
{
|
||||
/* 00 */ 0x02CB,
|
||||
/* 01 */ INVALID,
|
||||
/* 02 */ INVALID,
|
||||
/* 03 */ INVALID,
|
||||
},
|
||||
{
|
||||
/* 00 */ 0x02C9,
|
||||
/* 01 */ INVALID,
|
||||
|
@ -4548,7 +4542,13 @@ static const VXOpcodeTreeNode optreeMandatory[][4] =
|
|||
/* 03 */ INVALID,
|
||||
},
|
||||
{
|
||||
/* 00 */ 0x02D1,
|
||||
/* 00 */ 0x02DF,
|
||||
/* 01 */ INVALID,
|
||||
/* 02 */ INVALID,
|
||||
/* 03 */ INVALID,
|
||||
},
|
||||
{
|
||||
/* 00 */ 0x02CF,
|
||||
/* 01 */ INVALID,
|
||||
/* 02 */ INVALID,
|
||||
/* 03 */ INVALID,
|
||||
|
@ -4560,13 +4560,13 @@ static const VXOpcodeTreeNode optreeMandatory[][4] =
|
|||
/* 03 */ INVALID,
|
||||
},
|
||||
{
|
||||
/* 00 */ 0x02CE,
|
||||
/* 00 */ 0x02CC,
|
||||
/* 01 */ INVALID,
|
||||
/* 02 */ INVALID,
|
||||
/* 03 */ INVALID,
|
||||
},
|
||||
{
|
||||
/* 00 */ 0x02C7,
|
||||
/* 00 */ 0x02C8,
|
||||
/* 01 */ INVALID,
|
||||
/* 02 */ INVALID,
|
||||
/* 03 */ INVALID,
|
||||
|
@ -4595,12 +4595,6 @@ static const VXOpcodeTreeNode optreeMandatory[][4] =
|
|||
/* 02 */ INVALID,
|
||||
/* 03 */ INVALID,
|
||||
},
|
||||
{
|
||||
/* 00 */ 0x02D8,
|
||||
/* 01 */ INVALID,
|
||||
/* 02 */ INVALID,
|
||||
/* 03 */ INVALID,
|
||||
},
|
||||
{
|
||||
/* 00 */ 0x02D6,
|
||||
/* 01 */ INVALID,
|
||||
|
@ -4608,13 +4602,19 @@ static const VXOpcodeTreeNode optreeMandatory[][4] =
|
|||
/* 03 */ INVALID,
|
||||
},
|
||||
{
|
||||
/* 00 */ 0x02D9,
|
||||
/* 00 */ 0x02D4,
|
||||
/* 01 */ INVALID,
|
||||
/* 02 */ INVALID,
|
||||
/* 03 */ INVALID,
|
||||
},
|
||||
{
|
||||
/* 00 */ 0x02D4,
|
||||
/* 00 */ 0x02D7,
|
||||
/* 01 */ INVALID,
|
||||
/* 02 */ INVALID,
|
||||
/* 03 */ INVALID,
|
||||
},
|
||||
{
|
||||
/* 00 */ 0x02D2,
|
||||
/* 01 */ INVALID,
|
||||
/* 02 */ INVALID,
|
||||
/* 03 */ INVALID,
|
||||
|
@ -5828,8 +5828,8 @@ static const VXOpcodeTreeNode optreeX87[][64] =
|
|||
static const VXOpcodeTreeNode optreeAddressSize[][3] =
|
||||
{
|
||||
{
|
||||
/* 00 */ 0x02CF,
|
||||
/* 01 */ 0x02D2,
|
||||
/* 00 */ 0x02CD,
|
||||
/* 01 */ 0x02D0,
|
||||
/* 02 */ 0x02EC,
|
||||
},
|
||||
};
|
||||
|
@ -6133,7 +6133,7 @@ static const VXOpcodeTreeNode optreeMode[][2] =
|
|||
/* 01 */ INVALID,
|
||||
},
|
||||
{
|
||||
/* 00 */ 0x02DF,
|
||||
/* 00 */ 0x02DC,
|
||||
/* 01 */ INVALID,
|
||||
},
|
||||
{
|
||||
|
@ -6214,11 +6214,11 @@ static const VXOpcodeTreeNode optreeVendor[][2] =
|
|||
},
|
||||
{
|
||||
/* 00 */ INVALID,
|
||||
/* 01 */ 0x02C2,
|
||||
/* 01 */ 0x02C3,
|
||||
},
|
||||
{
|
||||
/* 00 */ INVALID,
|
||||
/* 01 */ 0x02C3,
|
||||
/* 01 */ 0x02C2,
|
||||
},
|
||||
{
|
||||
/* 00 */ INVALID,
|
||||
|
@ -7573,36 +7573,36 @@ static const VXInstructionDefinition instrDefinitions[] =
|
|||
/* 2BF */ { VXInstructionMnemonic::INVEPT, { OPI_Gq, OPI_Mo, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2C0 */ { VXInstructionMnemonic::INVLPG, { OPI_M, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_ADDRESS_SIZE_PREFIX | IDF_ACCEPTS_REXR | IDF_ACCEPTS_REXX | IDF_ACCEPTS_REXB },
|
||||
/* 2C1 */ { VXInstructionMnemonic::INVLPGA, { OPI_NONE, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2C2 */ { VXInstructionMnemonic::INVVPID, { OPI_Gd, OPI_Mo, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2C3 */ { VXInstructionMnemonic::INVVPID, { OPI_Gq, OPI_Mo, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2C2 */ { VXInstructionMnemonic::INVVPID, { OPI_Gq, OPI_Mo, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2C3 */ { VXInstructionMnemonic::INVVPID, { OPI_Gd, OPI_Mo, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2C4 */ { VXInstructionMnemonic::IRETD, { OPI_NONE, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_ACCEPTS_REXW },
|
||||
/* 2C5 */ { VXInstructionMnemonic::IRETQ, { OPI_NONE, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_ACCEPTS_REXW },
|
||||
/* 2C6 */ { VXInstructionMnemonic::IRETW, { OPI_NONE, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_ACCEPTS_REXW },
|
||||
/* 2C7 */ { VXInstructionMnemonic::JA, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2C8 */ { VXInstructionMnemonic::JA, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2C9 */ { VXInstructionMnemonic::JAE, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2CA */ { VXInstructionMnemonic::JAE, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2CB */ { VXInstructionMnemonic::JB, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2CC */ { VXInstructionMnemonic::JB, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2CD */ { VXInstructionMnemonic::JBE, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2CE */ { VXInstructionMnemonic::JBE, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2CF */ { VXInstructionMnemonic::JCXZ, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_ADDRESS_SIZE_PREFIX },
|
||||
/* 2D0 */ { VXInstructionMnemonic::JE, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2D1 */ { VXInstructionMnemonic::JE, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2D2 */ { VXInstructionMnemonic::JECXZ, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_ADDRESS_SIZE_PREFIX },
|
||||
/* 2D3 */ { VXInstructionMnemonic::JG, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2D4 */ { VXInstructionMnemonic::JG, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2D5 */ { VXInstructionMnemonic::JGE, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2D6 */ { VXInstructionMnemonic::JGE, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2D7 */ { VXInstructionMnemonic::JL, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2D8 */ { VXInstructionMnemonic::JL, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2D9 */ { VXInstructionMnemonic::JLE, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2DA */ { VXInstructionMnemonic::JLE, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2DB */ { VXInstructionMnemonic::JMP, { OPI_Fv, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_ADDRESS_SIZE_PREFIX | IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_ACCEPTS_REXW | IDF_ACCEPTS_REXR | IDF_ACCEPTS_REXX | IDF_ACCEPTS_REXB },
|
||||
/* 2DC */ { VXInstructionMnemonic::JMP, { OPI_Ev, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_ADDRESS_SIZE_PREFIX | IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_ACCEPTS_REXW | IDF_ACCEPTS_REXR | IDF_ACCEPTS_REXX | IDF_ACCEPTS_REXB | IDF_DEFAULT_64 },
|
||||
/* 2DD */ { VXInstructionMnemonic::JMP, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_ACCEPTS_REXW | IDF_DEFAULT_64 },
|
||||
/* 2DE */ { VXInstructionMnemonic::JMP, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_DEFAULT_64 },
|
||||
/* 2DF */ { VXInstructionMnemonic::JMP, { OPI_Av, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX },
|
||||
/* 2C7 */ { VXInstructionMnemonic::JA, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2C8 */ { VXInstructionMnemonic::JA, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2C9 */ { VXInstructionMnemonic::JB, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2CA */ { VXInstructionMnemonic::JB, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2CB */ { VXInstructionMnemonic::JBE, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2CC */ { VXInstructionMnemonic::JBE, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2CD */ { VXInstructionMnemonic::JCXZ, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_ADDRESS_SIZE_PREFIX },
|
||||
/* 2CE */ { VXInstructionMnemonic::JE, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2CF */ { VXInstructionMnemonic::JE, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2D0 */ { VXInstructionMnemonic::JECXZ, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_ADDRESS_SIZE_PREFIX },
|
||||
/* 2D1 */ { VXInstructionMnemonic::JG, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2D2 */ { VXInstructionMnemonic::JG, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2D3 */ { VXInstructionMnemonic::JGE, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2D4 */ { VXInstructionMnemonic::JGE, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2D5 */ { VXInstructionMnemonic::JL, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2D6 */ { VXInstructionMnemonic::JL, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2D7 */ { VXInstructionMnemonic::JLE, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2D8 */ { VXInstructionMnemonic::JLE, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2D9 */ { VXInstructionMnemonic::JMP, { OPI_Ev, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_ADDRESS_SIZE_PREFIX | IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_ACCEPTS_REXW | IDF_ACCEPTS_REXR | IDF_ACCEPTS_REXX | IDF_ACCEPTS_REXB | IDF_DEFAULT_64 },
|
||||
/* 2DA */ { VXInstructionMnemonic::JMP, { OPI_Fv, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_ADDRESS_SIZE_PREFIX | IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_ACCEPTS_REXW | IDF_ACCEPTS_REXR | IDF_ACCEPTS_REXX | IDF_ACCEPTS_REXB },
|
||||
/* 2DB */ { VXInstructionMnemonic::JMP, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_ACCEPTS_REXW | IDF_DEFAULT_64 },
|
||||
/* 2DC */ { VXInstructionMnemonic::JMP, { OPI_Av, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX },
|
||||
/* 2DD */ { VXInstructionMnemonic::JMP, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_DEFAULT_64 },
|
||||
/* 2DE */ { VXInstructionMnemonic::JNB, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2DF */ { VXInstructionMnemonic::JNB, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2E0 */ { VXInstructionMnemonic::JNE, { OPI_Jz, OPI_NONE, OPI_NONE, OPI_NONE }, IDF_ACCEPTS_OPERAND_SIZE_PREFIX | IDF_DEFAULT_64 },
|
||||
/* 2E1 */ { VXInstructionMnemonic::JNE, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
/* 2E2 */ { VXInstructionMnemonic::JNO, { OPI_Jb, OPI_NONE, OPI_NONE, OPI_NONE }, 0 },
|
||||
|
@ -8992,17 +8992,17 @@ const char* instrMnemonicStrings[] =
|
|||
/* 0F3 */ "iretq",
|
||||
/* 0F4 */ "iretw",
|
||||
/* 0F5 */ "ja",
|
||||
/* 0F6 */ "jae",
|
||||
/* 0F7 */ "jb",
|
||||
/* 0F8 */ "jbe",
|
||||
/* 0F9 */ "jcxz",
|
||||
/* 0FA */ "je",
|
||||
/* 0FB */ "jecxz",
|
||||
/* 0FC */ "jg",
|
||||
/* 0FD */ "jge",
|
||||
/* 0FE */ "jl",
|
||||
/* 0FF */ "jle",
|
||||
/* 100 */ "jmp",
|
||||
/* 0F6 */ "jb",
|
||||
/* 0F7 */ "jbe",
|
||||
/* 0F8 */ "jcxz",
|
||||
/* 0F9 */ "je",
|
||||
/* 0FA */ "jecxz",
|
||||
/* 0FB */ "jg",
|
||||
/* 0FC */ "jge",
|
||||
/* 0FD */ "jl",
|
||||
/* 0FE */ "jle",
|
||||
/* 0FF */ "jmp",
|
||||
/* 100 */ "jnb",
|
||||
/* 101 */ "jne",
|
||||
/* 102 */ "jno",
|
||||
/* 103 */ "jnp",
|
||||
|
|
|
@ -291,17 +291,17 @@ enum class VXInstructionMnemonic : uint16_t
|
|||
/* 0F3 */ IRETQ,
|
||||
/* 0F4 */ IRETW,
|
||||
/* 0F5 */ JA,
|
||||
/* 0F6 */ JAE,
|
||||
/* 0F7 */ JB,
|
||||
/* 0F8 */ JBE,
|
||||
/* 0F9 */ JCXZ,
|
||||
/* 0FA */ JE,
|
||||
/* 0FB */ JECXZ,
|
||||
/* 0FC */ JG,
|
||||
/* 0FD */ JGE,
|
||||
/* 0FE */ JL,
|
||||
/* 0FF */ JLE,
|
||||
/* 100 */ JMP,
|
||||
/* 0F6 */ JB,
|
||||
/* 0F7 */ JBE,
|
||||
/* 0F8 */ JCXZ,
|
||||
/* 0F9 */ JE,
|
||||
/* 0FA */ JECXZ,
|
||||
/* 0FB */ JG,
|
||||
/* 0FC */ JGE,
|
||||
/* 0FD */ JL,
|
||||
/* 0FE */ JLE,
|
||||
/* 0FF */ JMP,
|
||||
/* 100 */ JNB,
|
||||
/* 101 */ JNE,
|
||||
/* 102 */ JNO,
|
||||
/* 103 */ JNP,
|
||||
|
@ -1608,7 +1608,7 @@ extern const char* instrMnemonicStrings[];
|
|||
* @param node The node.
|
||||
* @return The type of the specified opcode tree node.
|
||||
*/
|
||||
inline VXOpcodeTreeNodeType GetOpcodeNodeType(VXOpcodeTreeNode node)
|
||||
inline VXOpcodeTreeNodeType VDEGetOpcodeNodeType(VXOpcodeTreeNode node)
|
||||
{
|
||||
return static_cast<VXOpcodeTreeNodeType>((node >> 12) & 0x0F);
|
||||
}
|
||||
|
@ -1618,7 +1618,7 @@ inline VXOpcodeTreeNodeType GetOpcodeNodeType(VXOpcodeTreeNode node)
|
|||
* @param node The node.
|
||||
* @return The value of the specified opcode tree node.
|
||||
*/
|
||||
inline uint16_t GetOpcodeNodeValue(VXOpcodeTreeNode node)
|
||||
inline uint16_t VDEGetOpcodeNodeValue(VXOpcodeTreeNode node)
|
||||
{
|
||||
return (node & 0x0FFF);
|
||||
}
|
||||
|
@ -1627,7 +1627,7 @@ inline uint16_t GetOpcodeNodeValue(VXOpcodeTreeNode node)
|
|||
* @brief Returns the root node of the opcode tree.
|
||||
* @return The root node of the opcode tree.
|
||||
*/
|
||||
inline VXOpcodeTreeNode GetOpcodeTreeRoot()
|
||||
inline VXOpcodeTreeNode VDEGetOpcodeTreeRoot()
|
||||
{
|
||||
return 0x1000;
|
||||
}
|
||||
|
@ -1638,11 +1638,11 @@ inline VXOpcodeTreeNode GetOpcodeTreeRoot()
|
|||
* @param index The index of the child node to retrieve.
|
||||
* @return The specified child node.
|
||||
*/
|
||||
inline VXOpcodeTreeNode GetOpcodeTreeChild(VXOpcodeTreeNode parent, uint16_t index)
|
||||
inline VXOpcodeTreeNode VDEGetOpcodeTreeChild(VXOpcodeTreeNode parent, uint16_t index)
|
||||
{
|
||||
using namespace Internal;
|
||||
VXOpcodeTreeNodeType nodeType = GetOpcodeNodeType(parent);
|
||||
uint16_t tableIndex = GetOpcodeNodeValue(parent);
|
||||
VXOpcodeTreeNodeType nodeType = VDEGetOpcodeNodeType(parent);
|
||||
uint16_t tableIndex = VDEGetOpcodeNodeValue(parent);
|
||||
switch (nodeType)
|
||||
{
|
||||
case VXOpcodeTreeNodeType::TABLE:
|
||||
|
@ -1698,9 +1698,9 @@ inline VXOpcodeTreeNode GetOpcodeTreeChild(VXOpcodeTreeNode parent, uint16_t ind
|
|||
* @param node The instruction definition node.
|
||||
* @return Pointer to the instruction definition.
|
||||
*/
|
||||
inline const VXInstructionDefinition* GetInstructionDefinition(VXOpcodeTreeNode node)
|
||||
inline const VXInstructionDefinition* VDEGetInstructionDefinition(VXOpcodeTreeNode node)
|
||||
{
|
||||
assert(GetOpcodeNodeType(node) == VXOpcodeTreeNodeType::INSTRUCTION_DEFINITION);
|
||||
assert(VDEGetOpcodeNodeType(node) == VXOpcodeTreeNodeType::INSTRUCTION_DEFINITION);
|
||||
return &instrDefinitions[node & 0x0FFF];
|
||||
}
|
||||
|
||||
|
@ -1709,7 +1709,7 @@ inline const VXInstructionDefinition* GetInstructionDefinition(VXOpcodeTreeNode
|
|||
* @param mnemonic The mnemonic.
|
||||
* @return The instruction mnemonic string.
|
||||
*/
|
||||
inline const char* GetInstructionMnemonicString(VXInstructionMnemonic mnemonic)
|
||||
inline const char* VDEGetInstructionMnemonicString(VXInstructionMnemonic mnemonic)
|
||||
{
|
||||
return instrMnemonicStrings[static_cast<uint16_t>(mnemonic)];
|
||||
}
|
||||
|
@ -1719,7 +1719,7 @@ inline const char* GetInstructionMnemonicString(VXInstructionMnemonic mnemonic)
|
|||
* @param operandSize The defined operand size.
|
||||
* @return The the numeric value for the simple operand size definition.
|
||||
*/
|
||||
inline uint16_t GetSimpleOperandSize(VXDefinedOperandSize operandSize)
|
||||
inline uint16_t VDEGetSimpleOperandSize(VXDefinedOperandSize operandSize)
|
||||
{
|
||||
static uint16_t operandSizes[8] =
|
||||
{
|
||||
|
@ -1736,7 +1736,7 @@ inline uint16_t GetSimpleOperandSize(VXDefinedOperandSize operandSize)
|
|||
* @param operandSize The defined operand size.
|
||||
* @return The memory-size part of the operand size definition.
|
||||
*/
|
||||
inline VXDefinedOperandSize GetComplexOperandMemSize(VXDefinedOperandSize operandSize)
|
||||
inline VXDefinedOperandSize VDEGetComplexOperandMemSize(VXDefinedOperandSize operandSize)
|
||||
{
|
||||
return static_cast<VXDefinedOperandSize>(static_cast<uint8_t>(operandSize) & 0x0F);
|
||||
}
|
||||
|
@ -1746,7 +1746,7 @@ inline VXDefinedOperandSize GetComplexOperandMemSize(VXDefinedOperandSize operan
|
|||
* @param operandSize The defined operand size.
|
||||
* @return The register-size part of the operand size definition.
|
||||
*/
|
||||
inline VXDefinedOperandSize GetComplexOperandRegSize(VXDefinedOperandSize operandSize)
|
||||
inline VXDefinedOperandSize VDEGetComplexOperandRegSize(VXDefinedOperandSize operandSize)
|
||||
{
|
||||
return static_cast<VXDefinedOperandSize>((static_cast<uint8_t>(operandSize) >> 4) & 0x0F);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="VXDisassembler.h" />
|
||||
<ClInclude Include="VXDisassemblerUtils.h" />
|
||||
<ClInclude Include="VXDisassemblerTypes.h" />
|
||||
<ClInclude Include="VXInstructionDecoder.h" />
|
||||
<ClInclude Include="VXInstructionFormatter.h" />
|
||||
|
@ -27,6 +28,7 @@
|
|||
<ClInclude Include="VXSymbolResolver.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="VXDisassemblerUtils.cpp" />
|
||||
<ClCompile Include="VXInstructionDecoder.cpp" />
|
||||
<ClCompile Include="VXInstructionFormatter.cpp" />
|
||||
<ClCompile Include="VXOpcodeTable.cpp" />
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
<ClInclude Include="VXInstructionFormatter.h" />
|
||||
<ClInclude Include="VXOpcodeTable.h" />
|
||||
<ClInclude Include="VXSymbolResolver.h" />
|
||||
<ClInclude Include="VXDisassemblerUtils.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="VXInstructionDecoder.cpp" />
|
||||
<ClCompile Include="VXInstructionFormatter.cpp" />
|
||||
<ClCompile Include="VXOpcodeTable.cpp" />
|
||||
<ClCompile Include="VXSymbolResolver.cpp" />
|
||||
<ClCompile Include="VXDisassemblerUtils.cpp" />
|
||||
</ItemGroup>
|
||||
</Project>
|
Loading…
Reference in New Issue