Remove accessType from BackPatch's signature in favor of getting it from DisassembleMov.
It isn't easily accessible with sigaction or Mach exceptions (well, requires an additional system call in the latter), and isn't necessary. (and get rid of the enum, because it's only used once, and the comments are more expressive than enum names)
This commit is contained in:
parent
e198e201e4
commit
4c3230bcde
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
#include "x64Analyzer.h"
|
#include "x64Analyzer.h"
|
||||||
|
|
||||||
bool DisassembleMov(const unsigned char *codePtr, InstructionInfo &info, int accessType)
|
bool DisassembleMov(const unsigned char *codePtr, InstructionInfo *info)
|
||||||
{
|
{
|
||||||
unsigned const char *startCodePtr = codePtr;
|
unsigned const char *startCodePtr = codePtr;
|
||||||
u8 rex = 0;
|
u8 rex = 0;
|
||||||
|
@ -12,11 +12,11 @@ bool DisassembleMov(const unsigned char *codePtr, InstructionInfo &info, int acc
|
||||||
u8 codeByte2 = 0;
|
u8 codeByte2 = 0;
|
||||||
|
|
||||||
//Check for regular prefix
|
//Check for regular prefix
|
||||||
info.operandSize = 4;
|
info->operandSize = 4;
|
||||||
info.zeroExtend = false;
|
info->zeroExtend = false;
|
||||||
info.signExtend = false;
|
info->signExtend = false;
|
||||||
info.hasImmediate = false;
|
info->hasImmediate = false;
|
||||||
info.isMemoryWrite = false;
|
info->isMemoryWrite = false;
|
||||||
|
|
||||||
u8 modRMbyte = 0;
|
u8 modRMbyte = 0;
|
||||||
u8 sibByte = 0;
|
u8 sibByte = 0;
|
||||||
|
@ -26,7 +26,7 @@ bool DisassembleMov(const unsigned char *codePtr, InstructionInfo &info, int acc
|
||||||
|
|
||||||
if (*codePtr == 0x66)
|
if (*codePtr == 0x66)
|
||||||
{
|
{
|
||||||
info.operandSize = 2;
|
info->operandSize = 2;
|
||||||
codePtr++;
|
codePtr++;
|
||||||
}
|
}
|
||||||
else if (*codePtr == 0x67)
|
else if (*codePtr == 0x67)
|
||||||
|
@ -40,7 +40,7 @@ bool DisassembleMov(const unsigned char *codePtr, InstructionInfo &info, int acc
|
||||||
rex = *codePtr;
|
rex = *codePtr;
|
||||||
if (rex & 8) //REX.W
|
if (rex & 8) //REX.W
|
||||||
{
|
{
|
||||||
info.operandSize = 8;
|
info->operandSize = 8;
|
||||||
}
|
}
|
||||||
codePtr++;
|
codePtr++;
|
||||||
}
|
}
|
||||||
|
@ -72,11 +72,11 @@ bool DisassembleMov(const unsigned char *codePtr, InstructionInfo &info, int acc
|
||||||
(codeByte2 & 0xF0) == 0x80 ||
|
(codeByte2 & 0xF0) == 0x80 ||
|
||||||
((codeByte2 & 0xF0) == 0xA0 && (codeByte2 & 0x07) <= 0x02) ||
|
((codeByte2 & 0xF0) == 0xA0 && (codeByte2 & 0x07) <= 0x02) ||
|
||||||
(codeByte2 & 0xF8) == 0xC8)
|
(codeByte2 & 0xF8) == 0xC8)
|
||||||
{
|
{
|
||||||
// No mod R/M byte
|
// No mod R/M byte
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
modRMbyte = *codePtr++;
|
modRMbyte = *codePtr++;
|
||||||
hasModRM = true;
|
hasModRM = true;
|
||||||
}
|
}
|
||||||
|
@ -85,21 +85,21 @@ bool DisassembleMov(const unsigned char *codePtr, InstructionInfo &info, int acc
|
||||||
if (hasModRM)
|
if (hasModRM)
|
||||||
{
|
{
|
||||||
ModRM mrm(modRMbyte, rex);
|
ModRM mrm(modRMbyte, rex);
|
||||||
info.regOperandReg = mrm.reg;
|
info->regOperandReg = mrm.reg;
|
||||||
if (mrm.mod < 3)
|
if (mrm.mod < 3)
|
||||||
{
|
{
|
||||||
if (mrm.rm == 4)
|
if (mrm.rm == 4)
|
||||||
{
|
{
|
||||||
//SIB byte
|
//SIB byte
|
||||||
sibByte = *codePtr++;
|
sibByte = *codePtr++;
|
||||||
info.scaledReg = (sibByte >> 3) & 7;
|
info->scaledReg = (sibByte >> 3) & 7;
|
||||||
info.otherReg = (sibByte & 7);
|
info->otherReg = (sibByte & 7);
|
||||||
if (rex & 2) info.scaledReg += 8;
|
if (rex & 2) info->scaledReg += 8;
|
||||||
if (rex & 1) info.otherReg += 8;
|
if (rex & 1) info->otherReg += 8;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//info.scaledReg =
|
//info->scaledReg =
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mrm.mod == 1 || mrm.mod == 2)
|
if (mrm.mod == 1 || mrm.mod == 2)
|
||||||
|
@ -112,100 +112,103 @@ bool DisassembleMov(const unsigned char *codePtr, InstructionInfo &info, int acc
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displacementSize == 1)
|
if (displacementSize == 1)
|
||||||
info.displacement = (s32)(s8)*codePtr;
|
info->displacement = (s32)(s8)*codePtr;
|
||||||
else
|
else
|
||||||
info.displacement = *((s32 *)codePtr);
|
info->displacement = *((s32 *)codePtr);
|
||||||
codePtr += displacementSize;
|
codePtr += displacementSize;
|
||||||
|
|
||||||
|
|
||||||
if (accessType == 1)
|
switch (codeByte)
|
||||||
{
|
{
|
||||||
info.isMemoryWrite = true;
|
// writes
|
||||||
//Write access
|
case 0xC6: // mem <- imm8
|
||||||
switch (codeByte)
|
|
||||||
{
|
{
|
||||||
case MOVE_8BIT: //move 8-bit immediate
|
info->isMemoryWrite = true;
|
||||||
{
|
info->hasImmediate = true;
|
||||||
info.hasImmediate = true;
|
info->immediate = *codePtr;
|
||||||
info.immediate = *codePtr;
|
codePtr++; //move past immediate
|
||||||
codePtr++; //move past immediate
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MOVE_16_32BIT: //move 16 or 32-bit immediate, easiest case for writes
|
|
||||||
{
|
|
||||||
if (info.operandSize == 2)
|
|
||||||
{
|
|
||||||
info.hasImmediate = true;
|
|
||||||
info.immediate = *(u16*)codePtr;
|
|
||||||
codePtr += 2;
|
|
||||||
}
|
|
||||||
else if (info.operandSize == 4)
|
|
||||||
{
|
|
||||||
info.hasImmediate = true;
|
|
||||||
info.immediate = *(u32*)codePtr;
|
|
||||||
codePtr += 4;
|
|
||||||
}
|
|
||||||
else if (info.operandSize == 8)
|
|
||||||
{
|
|
||||||
info.zeroExtend = true;
|
|
||||||
info.immediate = *(u32*)codePtr;
|
|
||||||
codePtr += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MOVE_REG_TO_MEM: //move reg to memory
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
PanicAlert("Unhandled disasm case in write handler!\n\nPlease implement or avoid.");
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
else
|
|
||||||
{
|
|
||||||
// Memory read
|
|
||||||
|
|
||||||
//mov eax, dword ptr [rax] == 8b 00
|
case 0xC7: // mem <- imm16/32
|
||||||
switch (codeByte)
|
|
||||||
{
|
{
|
||||||
case 0x0F:
|
info->isMemoryWrite = true;
|
||||||
|
if (info->operandSize == 2)
|
||||||
|
{
|
||||||
|
info->hasImmediate = true;
|
||||||
|
info->immediate = *(u16*)codePtr;
|
||||||
|
codePtr += 2;
|
||||||
|
}
|
||||||
|
else if (info->operandSize == 4)
|
||||||
|
{
|
||||||
|
info->hasImmediate = true;
|
||||||
|
info->immediate = *(u32*)codePtr;
|
||||||
|
codePtr += 4;
|
||||||
|
}
|
||||||
|
else if (info->operandSize == 8)
|
||||||
|
{
|
||||||
|
info->zeroExtend = true;
|
||||||
|
info->immediate = *(u32*)codePtr;
|
||||||
|
codePtr += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x89: // mem <- r16/32/64
|
||||||
|
{
|
||||||
|
info->isMemoryWrite = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x0F: // two-byte escape
|
||||||
|
{
|
||||||
|
info->isMemoryWrite = false;
|
||||||
switch (codeByte2)
|
switch (codeByte2)
|
||||||
{
|
{
|
||||||
case MOVZX_BYTE: //movzx on byte
|
case 0xB6: // movzx on byte
|
||||||
info.zeroExtend = true;
|
info->zeroExtend = true;
|
||||||
info.operandSize = 1;
|
info->operandSize = 1;
|
||||||
break;
|
break;
|
||||||
case MOVZX_SHORT: //movzx on short
|
case 0xB7: // movzx on short
|
||||||
info.zeroExtend = true;
|
info->zeroExtend = true;
|
||||||
info.operandSize = 2;
|
info->operandSize = 2;
|
||||||
break;
|
break;
|
||||||
case MOVSX_BYTE: //movsx on byte
|
case 0xBE: // movsx on byte
|
||||||
info.signExtend = true;
|
info->signExtend = true;
|
||||||
info.operandSize = 1;
|
info->operandSize = 1;
|
||||||
break;
|
break;
|
||||||
case MOVSX_SHORT: //movsx on short
|
case 0xBF: // movsx on short
|
||||||
info.signExtend = true;
|
info->signExtend = true;
|
||||||
info.operandSize = 2;
|
info->operandSize = 2;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x8a:
|
}
|
||||||
if (info.operandSize == 4)
|
|
||||||
|
case 0x8A: // r8 <- mem
|
||||||
|
{
|
||||||
|
info->isMemoryWrite = false;
|
||||||
|
if (info->operandSize == 4)
|
||||||
{
|
{
|
||||||
info.operandSize = 1;
|
info->operandSize = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
case 0x8b:
|
|
||||||
break; //it's OK don't need to do anything
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 0x8B: // r16/32/64 <- mem
|
||||||
|
{
|
||||||
|
info->isMemoryWrite = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
info.instructionSize = (int)(codePtr - startCodePtr);
|
info->instructionSize = (int)(codePtr - startCodePtr);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,21 +33,12 @@ struct ModRM
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
enum{
|
enum AccessType
|
||||||
MOVZX_BYTE = 0xB6, //movzx on byte
|
{
|
||||||
MOVZX_SHORT = 0xB7, //movzx on short
|
|
||||||
MOVSX_BYTE = 0xBE, //movsx on byte
|
|
||||||
MOVSX_SHORT = 0xBF, //movsx on short
|
|
||||||
MOVE_8BIT = 0xC6, //move 8-bit immediate
|
|
||||||
MOVE_16_32BIT = 0xC7, //move 16 or 32-bit immediate
|
|
||||||
MOVE_REG_TO_MEM = 0x89, //move reg to memory
|
|
||||||
};
|
|
||||||
|
|
||||||
enum AccessType{
|
|
||||||
OP_ACCESS_READ = 0,
|
OP_ACCESS_READ = 0,
|
||||||
OP_ACCESS_WRITE = 1
|
OP_ACCESS_WRITE = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
bool DisassembleMov(const unsigned char *codePtr, InstructionInfo &info, int accessType);
|
bool DisassembleMov(const unsigned char *codePtr, InstructionInfo *info);
|
||||||
|
|
||||||
#endif // _X64ANALYZER_H_
|
#endif // _X64ANALYZER_H_
|
||||||
|
|
|
@ -81,11 +81,9 @@ void sigsegv_handler(int signal, siginfo_t *info, void *raw_context)
|
||||||
|
|
||||||
u32 em_address = (u32)(bad_address - memspace_bottom);
|
u32 em_address = (u32)(bad_address - memspace_bottom);
|
||||||
|
|
||||||
int access_type = 0;
|
|
||||||
|
|
||||||
CONTEXT fake_ctx;
|
CONTEXT fake_ctx;
|
||||||
fake_ctx.reg_pc = ctx->arm_pc;
|
fake_ctx.reg_pc = ctx->arm_pc;
|
||||||
const u8 *new_rip = jit->BackPatch(fault_instruction_ptr, access_type, em_address, &fake_ctx);
|
const u8 *new_rip = jit->BackPatch(fault_instruction_ptr, em_address, &fake_ctx);
|
||||||
if (new_rip) {
|
if (new_rip) {
|
||||||
ctx->arm_pc = fake_ctx.reg_pc;
|
ctx->arm_pc = fake_ctx.reg_pc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ bool DisamLoadStore(const u32 inst, ARMReg &rD, u8 &accessSize, bool &Store)
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const u8 *JitArm::BackPatch(u8 *codePtr, int accessType, u32 emAddress, void *ctx_void)
|
const u8 *JitArm::BackPatch(u8 *codePtr, u32 emAddress, void *ctx_void)
|
||||||
{
|
{
|
||||||
// TODO: This ctx needs to be filled with our information
|
// TODO: This ctx needs to be filled with our information
|
||||||
CONTEXT *ctx = (CONTEXT *)ctx_void;
|
CONTEXT *ctx = (CONTEXT *)ctx_void;
|
||||||
|
|
|
@ -160,7 +160,7 @@ const u8 *TrampolineCache::GetWriteTrampoline(const InstructionInfo &info)
|
||||||
// 1) It's really necessary. We don't know anything about the context.
|
// 1) It's really necessary. We don't know anything about the context.
|
||||||
// 2) It doesn't really hurt. Only instructions that access I/O will get these, and there won't be
|
// 2) It doesn't really hurt. Only instructions that access I/O will get these, and there won't be
|
||||||
// that many of them in a typical program/game.
|
// that many of them in a typical program/game.
|
||||||
const u8 *Jitx86Base::BackPatch(u8 *codePtr, int accessType, u32 emAddress, void *ctx_void)
|
const u8 *Jitx86Base::BackPatch(u8 *codePtr, u32 emAddress, void *ctx_void)
|
||||||
{
|
{
|
||||||
#ifdef _M_X64
|
#ifdef _M_X64
|
||||||
CONTEXT *ctx = (CONTEXT *)ctx_void;
|
CONTEXT *ctx = (CONTEXT *)ctx_void;
|
||||||
|
@ -169,29 +169,15 @@ const u8 *Jitx86Base::BackPatch(u8 *codePtr, int accessType, u32 emAddress, void
|
||||||
return 0; // this will become a regular crash real soon after this
|
return 0; // this will become a regular crash real soon after this
|
||||||
|
|
||||||
InstructionInfo info;
|
InstructionInfo info;
|
||||||
if (!DisassembleMov(codePtr, info, accessType)) {
|
if (!DisassembleMov(codePtr, &info)) {
|
||||||
BackPatchError("BackPatch - failed to disassemble MOV instruction", codePtr, emAddress);
|
BackPatchError("BackPatch - failed to disassemble MOV instruction", codePtr, emAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
if (info.isMemoryWrite) {
|
|
||||||
if (!Memory::IsRAMAddress(emAddress, true)) {
|
|
||||||
PanicAlert("Exception: Caught write to invalid address %08x", emAddress);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
BackPatchError("BackPatch - determined that MOV is write, not yet supported and should have been caught before",
|
|
||||||
codePtr, emAddress);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if (info.otherReg != RBX)
|
if (info.otherReg != RBX)
|
||||||
PanicAlert("BackPatch : Base reg not RBX."
|
PanicAlert("BackPatch : Base reg not RBX."
|
||||||
"\n\nAttempted to access %08x.", emAddress);
|
"\n\nAttempted to access %08x.", emAddress);
|
||||||
|
|
||||||
if (accessType == OP_ACCESS_WRITE)
|
|
||||||
PanicAlert("BackPatch : Currently only supporting reads."
|
|
||||||
"\n\nAttempted to write to %08x.", emAddress);
|
|
||||||
|
|
||||||
if (accessType == 0)
|
if (!info.isMemoryWrite)
|
||||||
{
|
{
|
||||||
XEmitter emitter(codePtr);
|
XEmitter emitter(codePtr);
|
||||||
int bswapNopCount;
|
int bswapNopCount;
|
||||||
|
@ -205,8 +191,11 @@ const u8 *Jitx86Base::BackPatch(u8 *codePtr, int accessType, u32 emAddress, void
|
||||||
emitter.NOP((int)info.instructionSize + bswapNopCount - 5);
|
emitter.NOP((int)info.instructionSize + bswapNopCount - 5);
|
||||||
return codePtr;
|
return codePtr;
|
||||||
}
|
}
|
||||||
else if (accessType == 1)
|
else
|
||||||
{
|
{
|
||||||
|
PanicAlert("BackPatch : Currently only supporting reads."
|
||||||
|
"\n\nAttempted to write to %08x.", emAddress);
|
||||||
|
|
||||||
// TODO: special case FIFO writes. Also, support 32-bit mode.
|
// TODO: special case FIFO writes. Also, support 32-bit mode.
|
||||||
// Also, debug this so that it actually works correctly :P
|
// Also, debug this so that it actually works correctly :P
|
||||||
XEmitter emitter(codePtr - 2);
|
XEmitter emitter(codePtr - 2);
|
||||||
|
|
|
@ -74,7 +74,7 @@ public:
|
||||||
|
|
||||||
virtual void Jit(u32 em_address) = 0;
|
virtual void Jit(u32 em_address) = 0;
|
||||||
|
|
||||||
virtual const u8 *BackPatch(u8 *codePtr, int accessType, u32 em_address, void *ctx) = 0;
|
virtual const u8 *BackPatch(u8 *codePtr, u32 em_address, void *ctx) = 0;
|
||||||
|
|
||||||
virtual const CommonAsmRoutinesBase *GetAsmRoutines() = 0;
|
virtual const CommonAsmRoutinesBase *GetAsmRoutines() = 0;
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ protected:
|
||||||
public:
|
public:
|
||||||
JitBlockCache *GetBlockCache() { return &blocks; }
|
JitBlockCache *GetBlockCache() { return &blocks; }
|
||||||
|
|
||||||
const u8 *BackPatch(u8 *codePtr, int accessType, u32 em_address, void *ctx);
|
const u8 *BackPatch(u8 *codePtr, u32 em_address, void *ctx);
|
||||||
|
|
||||||
bool IsInCodeSpace(u8 *ptr) { return IsInSpace(ptr); }
|
bool IsInCodeSpace(u8 *ptr) { return IsInSpace(ptr); }
|
||||||
};
|
};
|
||||||
|
|
|
@ -175,9 +175,9 @@ namespace JitInterface
|
||||||
{
|
{
|
||||||
return jit->IsInCodeSpace(ptr);
|
return jit->IsInCodeSpace(ptr);
|
||||||
}
|
}
|
||||||
const u8 *BackPatch(u8 *codePtr, int accessType, u32 em_address, void *ctx)
|
const u8 *BackPatch(u8 *codePtr, u32 em_address, void *ctx)
|
||||||
{
|
{
|
||||||
return jit->BackPatch(codePtr, accessType, em_address, ctx);
|
return jit->BackPatch(codePtr, em_address, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClearCache()
|
void ClearCache()
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace JitInterface
|
||||||
|
|
||||||
// Memory Utilities
|
// Memory Utilities
|
||||||
bool IsInCodeSpace(u8 *ptr);
|
bool IsInCodeSpace(u8 *ptr);
|
||||||
const u8 *BackPatch(u8 *codePtr, int accessType, u32 em_address, void *ctx);
|
const u8 *BackPatch(u8 *codePtr, u32 em_address, void *ctx);
|
||||||
|
|
||||||
// used by JIT to read instructions
|
// used by JIT to read instructions
|
||||||
u32 Read_Opcode_JIT(const u32 _Address);
|
u32 Read_Opcode_JIT(const u32 _Address);
|
||||||
|
|
|
@ -99,7 +99,7 @@ LONG NTAPI Handler(PEXCEPTION_POINTERS pPtrs)
|
||||||
|
|
||||||
//We could emulate the memory accesses here, but then they would still be around to take up
|
//We could emulate the memory accesses here, but then they would still be around to take up
|
||||||
//execution resources. Instead, we backpatch into a generic memory call and retry.
|
//execution resources. Instead, we backpatch into a generic memory call and retry.
|
||||||
const u8 *new_rip = JitInterface::BackPatch(codePtr, accessType, emAddress, ctx);
|
const u8 *new_rip = JitInterface::BackPatch(codePtr, emAddress, ctx);
|
||||||
|
|
||||||
// Rip/Eip needs to be updated.
|
// Rip/Eip needs to be updated.
|
||||||
if (new_rip)
|
if (new_rip)
|
||||||
|
@ -214,7 +214,6 @@ void sigsegv_handler(int signal, siginfo_t *info, void *raw_context)
|
||||||
// Backpatch time.
|
// Backpatch time.
|
||||||
// Seems we'll need to disassemble to get access_type - that work is probably
|
// Seems we'll need to disassemble to get access_type - that work is probably
|
||||||
// best done and debugged on the Windows side.
|
// best done and debugged on the Windows side.
|
||||||
int access_type = 0;
|
|
||||||
|
|
||||||
CONTEXT fake_ctx;
|
CONTEXT fake_ctx;
|
||||||
|
|
||||||
|
@ -225,7 +224,7 @@ void sigsegv_handler(int signal, siginfo_t *info, void *raw_context)
|
||||||
fake_ctx.Eax = CREG_EAX(ctx);
|
fake_ctx.Eax = CREG_EAX(ctx);
|
||||||
fake_ctx.Eip = CREG_EIP(ctx);
|
fake_ctx.Eip = CREG_EIP(ctx);
|
||||||
#endif
|
#endif
|
||||||
const u8 *new_rip = jit->BackPatch(fault_instruction_ptr, access_type, em_address, &fake_ctx);
|
const u8 *new_rip = jit->BackPatch(fault_instruction_ptr, em_address, &fake_ctx);
|
||||||
if (new_rip)
|
if (new_rip)
|
||||||
{
|
{
|
||||||
#ifdef _M_X64
|
#ifdef _M_X64
|
||||||
|
|
Loading…
Reference in New Issue