void CreateTest(int bitness, string hexBytes, Func<Instruction> create) { var bytes = HexUtils.ToByteArray(hexBytes); var decoder = Decoder.Create(bitness, new ByteArrayCodeReader(bytes)); switch (bitness) { case 16: decoder.InstructionPointer = DecoderConstants.DEFAULT_IP16; break; case 32: decoder.InstructionPointer = DecoderConstants.DEFAULT_IP32; break; case 64: decoder.InstructionPointer = DecoderConstants.DEFAULT_IP64; break; default: throw new InvalidOperationException(); } var origRip = decoder.InstructionPointer; decoder.Decode(out var decodedInstr); decodedInstr.CodeSize = 0; decodedInstr.ByteLength = 0; decodedInstr.NextIP64 = 0; var createdInstr = create(); Assert.True(Instruction.TEST_BitByBitEquals(ref decodedInstr, ref createdInstr)); var writer = new CodeWriterImpl(); var encoder = decoder.CreateEncoder(writer); bool result = encoder.TryEncode(ref createdInstr, origRip, out _, out var errorMessage); Assert.Null(errorMessage); Assert.True(result); Assert.Equal(bytes, writer.ToArray()); }
public static IntPtr RunRemoteCode(IntPtr hProc, IReadOnlyList <Instruction> instructions, bool x86) { var cw = new CodeWriterImpl(); var ib = new InstructionBlock(cw, new List <Instruction>(instructions), 0); if (!BlockEncoder.TryEncode(x86 ? 32 : 64, ib, out string?errMsg, out _)) { throw new Exception("Error during Iced encode: " + errMsg); } byte[] bytes = cw.ToArray(); var ptrStub = Native.VirtualAllocEx(hProc, IntPtr.Zero, (uint)bytes.Length, 0x1000, 0x40); Native.WriteProcessMemory(hProc, ptrStub, bytes, (uint)bytes.Length, out _); Console.WriteLine("Written to 0x" + ptrStub.ToInt64().ToString("X8")); #if DEBUG Console.WriteLine("Press ENTER to start remote thread (you have the chance to place a breakpoint now)"); Console.ReadLine(); #endif var thread = Native.CreateRemoteThread(hProc, IntPtr.Zero, 0u, ptrStub, IntPtr.Zero, 0u, IntPtr.Zero); // NOTE: could wait for thread to finish with WaitForSingleObject Debug.Assert(thread != IntPtr.Zero); return(thread); }
void Test_zero_bytes() { var a = new Assembler(64); var lblf = a.CreateLabel(); var lbll = a.CreateLabel(); var lbl1 = a.CreateLabel(); var lbl2 = a.CreateLabel(); a.Label(ref lblf); a.zero_bytes(); a.je(lbl1); a.je(lbl2); a.Label(ref lbl1); a.zero_bytes(); a.Label(ref lbl2); a.nop(); [email protected]_bytes(); a.Label(ref lbll); a.zero_bytes(); var writer = new CodeWriterImpl(); a.Assemble(writer, 0); var bytes = writer.ToArray(); Assert.Equal(new byte[] { 0x74, 0x02, 0x74, 0x00, 0x90 }, bytes); }
/// <summary> /// Encodes the original bytes for a new address fixing e.g. branches, calls, jumps for execution at a given address. /// </summary> /// <param name="newAddress">The new address to encode the original instructions for.</param> public byte[] EncodeForNewAddress(IntPtr newAddress) { var writer = new CodeWriterImpl(_bytes.Length * 2); var block = new InstructionBlock(writer, DecodePrologue(), (ulong)newAddress); BlockEncoder.TryEncode(_bitness, block, out _); return(writer.ToArray()); }
/// <summary> /// Encodes the original bytes for a new address fixing e.g. branches, calls, jumps for execution at a given address. /// </summary> /// <param name="newAddress">The new address to encode the original instructions for.</param> public byte[] EncodeForNewAddress(nuint newAddress) { var writer = new CodeWriterImpl(_bytes.Length * 2); var block = new InstructionBlock(writer, DecodePrologue(), newAddress); if (!BlockEncoder.TryEncode(_bitness, block, out var error, out _)) { throw new Exception($"Reloaded Hooks: Internal Error in {nameof(Reloaded.Hooks.Internal)}/{nameof(IcedPatcher)}. " + $"Failed to re-encode code for new address. Process will probably die." + $"Error: {error}"); } return(writer.ToArray()); }
void Anonymous_labels() { var c = new Assembler(64); var lbl1 = c.CreateLabel(); var lbl2 = c.CreateLabel(); var lbl3 = c.CreateLabel(); var lbl4 = c.CreateLabel(); c.Label(ref lbl1); c.inc(eax); c.nop(); c.AnonymousLabel(); c.je(c.@B); c.nop(); c.Label(ref lbl2); c.je(c.@B); c.nop(); c.jmp(lbl1); c.nop(); c.jmp(lbl2); c.nop(); c.jmp(lbl3); c.nop(); c.jmp(lbl4); c.nop(); c.jne(c.@F); c.nop(); c.Label(ref lbl3); c.jne(c.@F); c.nop(); c.AnonymousLabel(); c.inc(eax); c.nop(); c.Label(ref lbl4); c.nop(); c.nop(); var expectedData = new byte[] { 0xFF, 0xC0, 0x90, 0x74, 0xFE, 0x90, 0x74, 0xFB, 0x90, 0xEB, 0xF5, 0x90, 0xEB, 0xF8, 0x90, 0xEB, 0x07, 0x90, 0xEB, 0x0A, 0x90, 0x75, 0x04, 0x90, 0x75, 0x01, 0x90, 0xFF, 0xC0, 0x90, 0x90, 0x90, }; var writer = new CodeWriterImpl(); c.Assemble(writer, 0); Assert.Equal(expectedData, writer.ToArray()); }
void Reset_works() { var c = new Assembler(Bitness); c.CreateLabel(); c.add(rax, rcx); _ = c.@lock; c.PreferVex = false; c.PreferBranchShort = false; c.Reset(); Assert.False(c.PreferVex); Assert.False(c.PreferBranchShort); Assert.Empty(c.Instructions); Assert.True(c.CurrentLabel.IsEmpty); var writer = new CodeWriterImpl(); var result = c.Assemble(writer, 0); Assert.Single(result.Result); Assert.Empty(writer.ToArray()); }
public void TestNops() { foreach (var bitness in new[] { 16, 32, 64 }) { for (int i = 0; i <= 128; i++) { var a = new Assembler(bitness); a.nop(i); var writer = new CodeWriterImpl(); a.Assemble(writer, 0); var data = writer.ToArray(); Assert.Equal(i, data.Length); var reader = new ByteArrayCodeReader(data); var decoder = Decoder.Create(bitness, reader); while (reader.CanReadByte) { var instr = decoder.Decode(); switch (instr.Code) { case Code.Nopw: case Code.Nopd: case Code.Nopq: case Code.Nop_rm16: case Code.Nop_rm32: case Code.Nop_rm64: break; default: Assert.True(false, $"Expected a NOP but got {instr.Code}"); break; } } } { var a = new Assembler(bitness); Assert.Throws <ArgumentOutOfRangeException>(() => a.nop(-1)); } } }
void TestVexEvexPrefixes() { var a = new Assembler(64); a.PreferVex = true; Assert.True(a.PreferVex); a.vaddpd(xmm1, xmm2, xmm3); a.vex.vaddpd(xmm1, xmm2, xmm3); a.vaddpd(xmm1, xmm2, xmm3); a.evex.vaddpd(xmm1, xmm2, xmm3); a.vaddpd(xmm1, xmm2, xmm3); Assert.True(a.PreferVex); a.PreferVex = false; Assert.False(a.PreferVex); a.vaddpd(xmm1, xmm2, xmm3); a.vex.vaddpd(xmm1, xmm2, xmm3); a.vaddpd(xmm1, xmm2, xmm3); a.evex.vaddpd(xmm1, xmm2, xmm3); a.vaddpd(xmm1, xmm2, xmm3); Assert.False(a.PreferVex); var writer = new CodeWriterImpl(); a.Assemble(writer, 0); var bytes = writer.ToArray(); Assert.Equal(new byte[] { 0xC5, 0xE9, 0x58, 0xCB, 0xC5, 0xE9, 0x58, 0xCB, 0xC5, 0xE9, 0x58, 0xCB, 0x62, 0xF1, 0xED, 0x08, 0x58, 0xCB, 0xC5, 0xE9, 0x58, 0xCB, 0x62, 0xF1, 0xED, 0x08, 0x58, 0xCB, 0xC5, 0xE9, 0x58, 0xCB, 0x62, 0xF1, 0xED, 0x08, 0x58, 0xCB, 0x62, 0xF1, 0xED, 0x08, 0x58, 0xCB, 0x62, 0xF1, 0xED, 0x08, 0x58, 0xCB, }, bytes); }
protected unsafe void TestAssemblerDeclareData <T>(Action <Assembler> fAsm, T[] data) where T : unmanaged { var assembler = new Assembler(Bitness); var sizeOfT = sizeof(T); fAsm(assembler); var writer = new CodeWriterImpl(); assembler.Assemble(writer, 0); var buffer = writer.ToArray(); Assert.Equal(sizeOfT * data.Length, buffer.Length); fixed(void *pData = data) { for (int i = 0; i < buffer.Length; i++) { var expectedData = ((byte *)pData)[i]; Assert.True(expectedData == buffer[i], $"Invalid data at offset {i}. Expecting {expectedData:x2}. Actual: {buffer[i]}"); } } }
/// <summary> /// Patches the prologue of a function with iced and returns the new address of the prologue. /// </summary> /// <returns></returns> public IntPtr GetFunctionAddress() { if (_newPrologueAddress != IntPtr.Zero) { return(_newPrologueAddress); } var estimateLength = _bytes.Length * 2; // Super generous! Exact length not known till relocated, just ensuring the size is enough under any circumstance. var buffer = Utilities.FindOrCreateBufferInRange(estimateLength); return(buffer.ExecuteWithLock(() => { // Patch prologue var newBaseAddress = buffer.Properties.WritePointer; var writer = new CodeWriterImpl(estimateLength); var block = new InstructionBlock((CodeWriter)writer, DecodePrologue(), (ulong)newBaseAddress); BlockEncoder.TryEncode(_bitness, block, out _); var data = writer.ToArray(); _newPrologueAddress = buffer.Add(data, 1); return _newPrologueAddress; })); }
private static IntPtr AllocateStub(IntPtr hProc, string asmPath, string typeName, string methodName, string?args, IntPtr fnAddr, bool x86, bool isV4) { const string clrVersion2 = "v2.0.50727"; const string clrVersion4 = "v4.0.30319"; string clrVersion = isV4 ? clrVersion4 : clrVersion2; const string buildFlavor = "wks"; // WorkStation var clsidCLRRuntimeHost = new Guid(0x90F1A06E, 0x7712, 0x4762, 0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02); var iidICLRRuntimeHost = new Guid(0x90F1A06C, 0x7712, 0x4762, 0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02); IntPtr ppv = alloc(IntPtr.Size); IntPtr riid = allocBytes(iidICLRRuntimeHost.ToByteArray()); IntPtr rcslid = allocBytes(clsidCLRRuntimeHost.ToByteArray()); IntPtr pwszBuildFlavor = allocString(buildFlavor); IntPtr pwszVersion = allocString(clrVersion); IntPtr pReturnValue = alloc(4); IntPtr pwzArgument = allocString(args); IntPtr pwzMethodName = allocString(methodName); IntPtr pwzTypeName = allocString(typeName); IntPtr pwzAssemblyPath = allocString(asmPath); var instructions = new InstructionList(); if (x86) { // call CorBindtoRuntimeEx instructions.Add(Instruction.Create(Code.Pushd_imm32, ppv.ToInt32())); instructions.Add(Instruction.Create(Code.Pushd_imm32, riid.ToInt32())); instructions.Add(Instruction.Create(Code.Pushd_imm32, rcslid.ToInt32())); instructions.Add(Instruction.Create(Code.Pushd_imm8, 0)); // startupFlags instructions.Add(Instruction.Create(Code.Pushd_imm32, pwszBuildFlavor.ToInt32())); instructions.Add(Instruction.Create(Code.Pushd_imm32, pwszVersion.ToInt32())); instructions.Add(Instruction.Create(Code.Mov_r32_imm32, Register.EAX, fnAddr.ToInt32())); instructions.Add(Instruction.Create(Code.Call_rm32, Register.EAX)); // call ICLRRuntimeHost::Start instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EAX, new MemoryOperand(Register.None, ppv.ToInt32()))); instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.ECX, new MemoryOperand(Register.EAX))); instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EDX, new MemoryOperand(Register.ECX, 0x0C))); instructions.Add(Instruction.Create(Code.Push_r32, Register.EAX)); instructions.Add(Instruction.Create(Code.Call_rm32, Register.EDX)); // call ICLRRuntimeHost::ExecuteInDefaultAppDomain instructions.Add(Instruction.Create(Code.Pushd_imm32, pReturnValue.ToInt32())); instructions.Add(Instruction.Create(Code.Pushd_imm32, pwzArgument.ToInt32())); instructions.Add(Instruction.Create(Code.Pushd_imm32, pwzMethodName.ToInt32())); instructions.Add(Instruction.Create(Code.Pushd_imm32, pwzTypeName.ToInt32())); instructions.Add(Instruction.Create(Code.Pushd_imm32, pwzAssemblyPath.ToInt32())); instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EAX, new MemoryOperand(Register.None, ppv.ToInt32()))); instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.ECX, new MemoryOperand(Register.EAX))); instructions.Add(Instruction.Create(Code.Push_r32, Register.EAX)); instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EAX, new MemoryOperand(Register.ECX, 0x2C))); instructions.Add(Instruction.Create(Code.Call_rm32, Register.EAX)); instructions.Add(Instruction.Create(Code.Retnd)); } else { const int maxStackIndex = 3; const int stackSize = 0x20 + maxStackIndex * 8; // 0x20 bytes shadow space, because x64 MemoryOperand stackAccess(int i) => new MemoryOperand(Register.RSP, stackSize - (maxStackIndex - i) * 8); instructions.Add(Instruction.Create(Code.Sub_rm64_imm8, Register.RSP, stackSize)); // call CorBindtoRuntimeEx // calling convention: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019 instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RAX, ppv.ToInt64())); instructions.Add(Instruction.Create(Code.Mov_rm64_r64, stackAccess(1), Register.RAX)); instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RAX, riid.ToInt64())); instructions.Add(Instruction.Create(Code.Mov_rm64_r64, stackAccess(0), Register.RAX)); instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.R9, rcslid.ToInt64())); instructions.Add(Instruction.Create(Code.Mov_r32_imm32, Register.R8D, 0)); // startupFlags instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RDX, pwszBuildFlavor.ToInt64())); instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RCX, pwszVersion.ToInt64())); instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RAX, fnAddr.ToInt64())); instructions.Add(Instruction.Create(Code.Call_rm64, Register.RAX)); // call pClrHost->Start(); instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RCX, ppv.ToInt64())); instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RCX, new MemoryOperand(Register.RCX))); instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RAX, new MemoryOperand(Register.RCX))); instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RDX, new MemoryOperand(Register.RAX, 0x18))); instructions.Add(Instruction.Create(Code.Call_rm64, Register.RDX)); // call pClrHost->ExecuteInDefaultAppDomain() instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RDX, pReturnValue.ToInt64())); instructions.Add(Instruction.Create(Code.Mov_rm64_r64, stackAccess(1), Register.RDX)); instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RDX, pwzArgument.ToInt64())); instructions.Add(Instruction.Create(Code.Mov_rm64_r64, stackAccess(0), Register.RDX)); instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.R9, pwzMethodName.ToInt64())); instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.R8, pwzTypeName.ToInt64())); instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RDX, pwzAssemblyPath.ToInt64())); instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RCX, ppv.ToInt64())); instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RCX, new MemoryOperand(Register.RCX))); instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RAX, new MemoryOperand(Register.RCX))); instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RAX, new MemoryOperand(Register.RAX, 0x58))); instructions.Add(Instruction.Create(Code.Call_rm64, Register.RAX)); instructions.Add(Instruction.Create(Code.Add_rm64_imm8, Register.RSP, stackSize)); instructions.Add(Instruction.Create(Code.Retnq)); } var cw = new CodeWriterImpl(); var ib = new InstructionBlock(cw, instructions, 0); bool success = BlockEncoder.TryEncode(x86 ? 32 : 64, ib, out string errMsg); if (!success) { throw new Exception("Error during Iced encode: " + errMsg); } byte[] bytes = cw.ToArray(); var ptrStub = alloc(bytes.Length, 0x40); // RWX writeBytes(ptrStub, bytes); return(ptrStub); IntPtr alloc(int size, int protection = 0x04) => Native.VirtualAllocEx(hProc, IntPtr.Zero, (uint)size, 0x1000, protection); void writeBytes(IntPtr address, byte[] b) => Native.WriteProcessMemory(hProc, address, b, (uint)b.Length, out _); void writeString(IntPtr address, string str) => writeBytes(address, new UnicodeEncoding().GetBytes(str)); IntPtr allocString(string?str) { if (str is null) { return(IntPtr.Zero); } IntPtr pString = alloc(str.Length * 2 + 2); writeString(pString, str); return(pString); } IntPtr allocBytes(byte[] buffer) { IntPtr pBuffer = alloc(buffer.Length); writeBytes(pBuffer, buffer); return(pBuffer); } }
protected void TestAssembler(Action <Assembler> fAsm, Instruction expectedInst, LocalOpCodeFlags flags = LocalOpCodeFlags.None) { var assembler = new Assembler(_bitness); // Encode the instruction if ((flags & LocalOpCodeFlags.PreferVex) != 0) { assembler.PreferVex = true; } else if ((flags & LocalOpCodeFlags.PreferEvex) != 0) { assembler.PreferVex = false; } if ((flags & LocalOpCodeFlags.PreferBranchShort) != 0) { assembler.PreferBranchShort = true; } else if ((flags & LocalOpCodeFlags.PreferBranchNear) != 0) { assembler.PreferBranchShort = false; } fAsm(assembler); // Expecting only one instruction Assert.Equal(1, assembler.Instructions.Count); // Encode the instruction first to get any errors var writer = new CodeWriterImpl(); assembler.Assemble(writer, 0, (flags & LocalOpCodeFlags.BranchUlong) != 0 ? BlockEncoderOptions.None : BlockEncoderOptions.DontFixBranches); // Check that the instruction is the one expected if ((flags & LocalOpCodeFlags.Broadcast) != 0) { expectedInst.IsBroadcast = true; } var inst = assembler.Instructions[0]; Assert.Equal(expectedInst, inst); // Special for decoding options DecoderOptions decoderOptions = DecoderOptions.None; switch (inst.Code) { case Code.Umov_rm8_r8: case Code.Umov_rm16_r16: case Code.Umov_rm32_r32: case Code.Umov_r8_rm8: case Code.Umov_r16_rm16: case Code.Umov_r32_rm32: decoderOptions = DecoderOptions.Umov; break; case Code.Xbts_r16_rm16: case Code.Xbts_r32_rm32: case Code.Ibts_rm16_r16: case Code.Ibts_rm32_r32: decoderOptions = DecoderOptions.Xbts; break; case Code.Frstpm: case Code.Fstdw_AX: case Code.Fstsg_AX: decoderOptions = DecoderOptions.OldFpu; break; case Code.Pcommit: decoderOptions = DecoderOptions.Pcommit; break; case Code.Loadall386: decoderOptions = DecoderOptions.Loadall386; break; case Code.Cl1invmb: decoderOptions = DecoderOptions.Cl1invmb; break; case Code.Mov_r32_tr: case Code.Mov_tr_r32: decoderOptions = DecoderOptions.MovTr; break; case Code.Jmpe_rm16: case Code.Jmpe_rm32: case Code.Jmpe_disp16: case Code.Jmpe_disp32: decoderOptions = DecoderOptions.Jmpe; break; case Code.ReservedNop_rm16_r16_0F0D: case Code.ReservedNop_rm32_r32_0F0D: case Code.ReservedNop_rm64_r64_0F0D: case Code.ReservedNop_rm16_r16_0F18: case Code.ReservedNop_rm32_r32_0F18: case Code.ReservedNop_rm64_r64_0F18: case Code.ReservedNop_rm16_r16_0F19: case Code.ReservedNop_rm32_r32_0F19: case Code.ReservedNop_rm64_r64_0F19: case Code.ReservedNop_rm16_r16_0F1A: case Code.ReservedNop_rm32_r32_0F1A: case Code.ReservedNop_rm64_r64_0F1A: case Code.ReservedNop_rm16_r16_0F1B: case Code.ReservedNop_rm32_r32_0F1B: case Code.ReservedNop_rm64_r64_0F1B: case Code.ReservedNop_rm16_r16_0F1C: case Code.ReservedNop_rm32_r32_0F1C: case Code.ReservedNop_rm64_r64_0F1C: case Code.ReservedNop_rm16_r16_0F1D: case Code.ReservedNop_rm32_r32_0F1D: case Code.ReservedNop_rm64_r64_0F1D: case Code.ReservedNop_rm16_r16_0F1E: case Code.ReservedNop_rm32_r32_0F1E: case Code.ReservedNop_rm64_r64_0F1E: case Code.ReservedNop_rm16_r16_0F1F: case Code.ReservedNop_rm32_r32_0F1F: case Code.ReservedNop_rm64_r64_0F1F: decoderOptions = DecoderOptions.ForceReservedNop; break; } if ((flags & LocalOpCodeFlags.BranchUlong) == 0) { decoderOptions |= DecoderOptions.AmdBranches; } // Check decoding back against the original instruction var instructionAsBytes = new System.Text.StringBuilder(); foreach (var b in writer.ToArray()) { instructionAsBytes.Append($"{b:x2} "); } var decoder = Decoder.Create(_bitness, new ByteArrayCodeReader(writer.ToArray()), decoderOptions); var decodedInst = decoder.Decode(); if ((flags & LocalOpCodeFlags.Fwait) != 0) { Assert.Equal(decodedInst, Instruction.Create(Code.Wait)); decodedInst = decoder.Decode(); switch (decodedInst.Code) { case Code.Fnstenv_m14byte: decodedInst.Code = Code.Fstenv_m14byte; break; case Code.Fnstenv_m28byte: decodedInst.Code = Code.Fstenv_m28byte; break; case Code.Fnstcw_m2byte: decodedInst.Code = Code.Fstcw_m2byte; break; case Code.Fneni: decodedInst.Code = Code.Feni; break; case Code.Fndisi: decodedInst.Code = Code.Fdisi; break; case Code.Fnclex: decodedInst.Code = Code.Fclex; break; case Code.Fninit: decodedInst.Code = Code.Finit; break; case Code.Fnsetpm: decodedInst.Code = Code.Fsetpm; break; case Code.Fnsave_m94byte: decodedInst.Code = Code.Fsave_m94byte; break; case Code.Fnsave_m108byte: decodedInst.Code = Code.Fsave_m108byte; break; case Code.Fnstsw_m2byte: decodedInst.Code = Code.Fstsw_m2byte; break; case Code.Fnstsw_AX: decodedInst.Code = Code.Fstsw_AX; break; } } // Reset IP to 0 when matching against decode if (inst.Code != Code.Jmpe_disp16 && inst.Code != Code.Jmpe_disp32 && (flags & LocalOpCodeFlags.Branch) != 0) { // Check if it's a label ID, if so, replace it with the IP if (inst.NearBranch64 == 1) { inst.NearBranch64 = 0; } } // Special case for branch via ulong. An instruction like `loopne 000031D0h` // could have been encoded with a sequence like: // // 0: e0 02 loopne 0x4 // 2: eb 05 jmp 0x9 // 4: e9 c7 31 00 00 jmp 0x31d0 if ((flags & LocalOpCodeFlags.BranchUlong) != 0) { Assert.Equal(inst.Code.ToShortBranch(), decodedInst.Code.ToShortBranch()); if (decodedInst.NearBranch64 == 4) { var nextDecodedInst = decoder.Decode(); var expectedCode = Bitness switch { 16 => Code.Jmp_rel8_16, 32 => Code.Jmp_rel8_32, 64 => Code.Jmp_rel8_64, _ => throw new InvalidOperationException(), }; Assert.True(nextDecodedInst.Code == expectedCode, $"Branch ulong next decoding failed!\nExpected: {expectedCode} \nActual Decoded: {nextDecodedInst}\n"); } else { Assert.True(inst.NearBranch64 == decodedInst.NearBranch64, $"Branch decoding offset failed!\nExpected: {inst} ({instructionAsBytes})\nActual Decoded: {decodedInst}\n"); } } else { Assert.True(inst == decodedInst, $"Decoding failed!\nExpected: {inst} ({instructionAsBytes})\nActual Decoded: {decodedInst}\n"); } }