• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SamboyCoding / Cpp2IL / 11073077703

27 Sep 2024 03:06PM UTC coverage: 28.369% (-0.01%) from 28.383%
11073077703

Pull #344

github

web-flow
Merge 72650ece3 into 2f8e5b3fc
Pull Request #344: Use ShiftStack and move instead of push and pop in X86InstructionSet

1238 of 6098 branches covered (20.3%)

Branch coverage included in aggregate %.

0 of 6 new or added lines in 1 file covered. (0.0%)

2 existing lines in 1 file now uncovered.

3341 of 10043 relevant lines covered (33.27%)

101977.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.15
/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using Cpp2IL.Core.Api;
5
using Cpp2IL.Core.Extensions;
6
using Cpp2IL.Core.Il2CppApiFunctions;
7
using Cpp2IL.Core.ISIL;
8
using Cpp2IL.Core.Logging;
9
using Cpp2IL.Core.Model.Contexts;
10
using Cpp2IL.Core.Utils;
11
using Iced.Intel;
12
using LibCpp2IL.BinaryStructures;
13

14
namespace Cpp2IL.Core.InstructionSets;
15

16
// This is honestly an X64InstructionSet by all means. Everything here screams "I AM X64".
17
public class X86InstructionSet : Cpp2IlInstructionSet
18
{
19
    private static readonly MasmFormatter Formatter = new();
×
20
    private static readonly StringOutput Output = new();
×
21

22
    private static string FormatInstructionInternal(Instruction instruction)
23
    {
24
        Formatter.Format(instruction, Output);
×
25
        return Output.ToStringAndReset();
×
26
    }
27

28
    public static string FormatInstruction(Instruction instruction)
29
    {
30
        lock (Formatter)
×
31
        {
32
            return FormatInstructionInternal(instruction);
×
33
        }
34
    }
×
35

36
    public override Memory<byte> GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) => X86Utils.GetRawManagedOrCaCacheGenMethodBody(context.UnderlyingPointer, isAttributeGenerator);
288,096✔
37

38
    public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance() => new X86KeyFunctionAddresses();
×
39

40
    public override string PrintAssembly(MethodAnalysisContext context)
41
    {
42
        lock (Formatter)
×
43
        {
44
            var insns = X86Utils.Iterate(X86Utils.GetRawManagedOrCaCacheGenMethodBody(context.UnderlyingPointer, false), context.UnderlyingPointer);
×
45

46
            return string.Join("\n", insns.Select(FormatInstructionInternal));
×
47
        }
48
    }
×
49

50
    public override List<InstructionSetIndependentInstruction> GetIsilFromMethod(MethodAnalysisContext context)
51
    {
52
        var builder = new IsilBuilder();
×
53

54
        foreach (var instruction in X86Utils.Iterate(context.RawBytes, context.UnderlyingPointer))
×
55
        {
56
            ConvertInstructionStatement(instruction, builder, context);
×
57
        }
58

59
        builder.FixJumps();
×
60

61
        return builder.BackingStatementList;
×
62
    }
63

64

65
    private void ConvertInstructionStatement(Instruction instruction, IsilBuilder builder, MethodAnalysisContext context)
66
    {
67
        var callNoReturn = false;
×
68
        int operandSize;
69

70
        switch (instruction.Mnemonic)
×
71
        {
72
            case Mnemonic.Mov:
73
            case Mnemonic.Movzx: // For all intents and purposes we don't care about zero-extending
74
            case Mnemonic.Movsx: // move with sign-extendign
75
            case Mnemonic.Movsxd: // same
76
            case Mnemonic.Movaps: // Movaps is basically just a mov but with the potential future detail that the size is dependent on reg size
77
            case Mnemonic.Movups: // Movaps but unaligned
78
            case Mnemonic.Movss: // Same as movaps but for floats
79
            case Mnemonic.Movd: // Mov but specifically dword
80
            case Mnemonic.Movq: // Mov but specifically qword
81
            case Mnemonic.Movsd: // Mov but specifically double
82
            case Mnemonic.Movdqa: // Movaps but multiple integers at once in theory
83
            case Mnemonic.Cvtdq2ps: // Technically a convert double to single, but for analysis purposes we can just treat it as a move
84
            case Mnemonic.Cvtps2pd: // same, but float to double
85
            case Mnemonic.Cvttsd2si: // same, but double to integer
86
            case Mnemonic.Movdqu: // DEST[127:0] := SRC[127:0]
87
                builder.Move(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
88
                break;
×
89
            case Mnemonic.Cbw: // AX := sign-extend AL
90
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.AX)), 
×
91
                    InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.AL)));
×
92
                break;
×
93
            case Mnemonic.Cwde: // EAX := sign-extend AX
94
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EAX)), 
×
95
                    InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.AX)));
×
96
                break;
×
97
            case Mnemonic.Cdqe: // RAX := sign-extend EAX
98
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.RAX)), 
×
99
                    InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EAX)));
×
100
                break;
×
101
            // it's very unsafe if there's been a jump to the next instruction here before.
102
            case Mnemonic.Cwd: // Convert Word to Doubleword
103
            {
104
                // The CWD instruction copies the sign (bit 15) of the value in the AX register into every bit position in the DX register
105
                var temp = InstructionSetIndependentOperand.MakeRegister("TEMP");
×
106
                builder.Move(instruction.IP, temp, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.AX))); // TEMP = AX
×
107
                builder.ShiftRight(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(15)); // TEMP >>= 15
×
108
                builder.Compare(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(1)); // temp == 1
×
109
                builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1);
×
110
                // temp == 1 ? DX := ushort.Max (1111111111) or DX := 0
111
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.DX)), InstructionSetIndependentOperand.MakeImmediate(ushort.MaxValue));
×
112
                builder.Goto(instruction.IP, instruction.IP + 2);
×
113
                builder.Move(instruction.IP + 1, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.DX)), InstructionSetIndependentOperand.MakeImmediate(0));
×
114
                builder.Nop(instruction.IP + 2);
×
115
                break;
×
116
            }
117
            case Mnemonic.Cdq: // Convert Doubleword to Quadword
118
            {
119
                // The CDQ instruction copies the sign (bit 31) of the value in the EAX register into every bit position in the EDX register.
120
                var temp = InstructionSetIndependentOperand.MakeRegister("TEMP");
×
121
                builder.Move(instruction.IP, temp, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EAX))); // TEMP = EAX
×
122
                builder.ShiftRight(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(31)); // TEMP >>= 31
×
123
                builder.Compare(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(1)); // temp == 1
×
124
                builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1);
×
125
                // temp == 1 ? EDX := uint.Max (1111111111) or EDX := 0
126
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EDX)), InstructionSetIndependentOperand.MakeImmediate(uint.MaxValue));
×
127
                builder.Goto(instruction.IP, instruction.IP + 2);
×
128
                builder.Move(instruction.IP + 1, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EDX)), InstructionSetIndependentOperand.MakeImmediate(0));
×
129
                builder.Nop(instruction.IP + 2);
×
130
                break;
×
131
            }
132
            case Mnemonic.Cqo: // same...
133
            {
134
                // The CQO instruction copies the sign (bit 63) of the value in the EAX register into every bit position in the RDX register.
135
                var temp = InstructionSetIndependentOperand.MakeRegister("TEMP");
×
136
                builder.Move(instruction.IP, temp, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.RAX))); // TEMP = RAX
×
137
                builder.ShiftRight(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(63)); // TEMP >>= 63
×
138
                builder.Compare(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(1)); // temp == 1
×
139
                builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1);
×
140
                // temp == 1 ? RDX := ulong.Max (1111111111) or RDX := 0
141
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.RDX)), InstructionSetIndependentOperand.MakeImmediate(ulong.MaxValue));
×
142
                builder.Goto(instruction.IP, instruction.IP + 2);
×
143
                builder.Move(instruction.IP + 1, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.RDX)), InstructionSetIndependentOperand.MakeImmediate(0));
×
144
                builder.Nop(instruction.IP + 2);
×
145
                break;
×
146
            }
147
            case Mnemonic.Lea:
148
                builder.LoadAddress(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
149
                break;
×
150
            case Mnemonic.Xor:
151
            case Mnemonic.Xorps: //xorps is just floating point xor
152
                if (instruction.Op0Kind == OpKind.Register && instruction.Op1Kind == OpKind.Register && instruction.Op0Register == instruction.Op1Register)
×
153
                    builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(0));
×
154
                else
155
                    builder.Xor(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
156
                break;
×
157
            case Mnemonic.Shl: // unsigned shift
158
            case Mnemonic.Sal: // signed shift
159
                builder.ShiftLeft(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
160
                break;
×
161
            case Mnemonic.Shr: // unsigned shift
162
            case Mnemonic.Sar: // signed shift
163
                builder.ShiftRight(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
164
                break;
×
165
            case Mnemonic.And:
166
            case Mnemonic.Andps: //Floating point and
167
                builder.And(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
168
                break;
×
169
            case Mnemonic.Or:
170
            case Mnemonic.Orps: //Floating point or
171
                builder.Or(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
172
                break;
×
173
            case Mnemonic.Not:
174
                builder.Neg(instruction.IP, ConvertOperand(instruction, 0));
×
175
                break;
×
176
            case Mnemonic.Neg: // dest := -dest
177
                builder.Neg(instruction.IP, ConvertOperand(instruction, 0));
×
178
                break;
×
179
            case Mnemonic.Imul:
180
                if (instruction.OpCount == 1)
×
181
                {
182
                    int opSize = instruction.Op0Kind == OpKind.Register ? instruction.Op0Register.GetSize() : instruction.MemorySize.GetSize();
×
183
                    switch (opSize) // TODO: I don't know how to work with dual registers here, I left hints though
184
                    {
185
                        case 1: // Op0 * AL -> AX
186
                            builder.Multiply(instruction.IP, Register.AX.MakeIndependent(), ConvertOperand(instruction, 0), Register.AL.MakeIndependent());
×
187
                            return;
×
188
                        case 2: // Op0 * AX -> DX:AX
189

190
                            break;
191
                        case 4: // Op0 * EAX -> EDX:EAX
192

193
                            break;
194
                        case 8: // Op0 * RAX -> RDX:RAX
195

196
                            break;
197
                        default: // prob 0, I think fallback to architecture alignment would be good here(issue: idk how to find out arch alignment)
198

199
                            break;
200
                    }
201

202
                    // if got to here, it didn't work
203
                    goto default;
204
                }
205
                else if (instruction.OpCount == 3) builder.Multiply(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
×
206
                else builder.Multiply(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
207

208
                break;
×
209
            case Mnemonic.Mulss:
210
            case Mnemonic.Vmulss:
211
                if (instruction.OpCount == 3)
×
212
                    builder.Multiply(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
×
213
                else if (instruction.OpCount == 2)
×
214
                    builder.Multiply(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
215
                else
216
                    goto default;
217

218
                break;
219
            
220
            case Mnemonic.Divss: // Divide Scalar Single Precision Floating-Point Values. DEST[31:0] = DEST[31:0] / SRC[31:0]
221
                builder.Divide(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
222
                break;
×
223
            case Mnemonic.Vdivss: // VEX Divide Scalar Single Precision Floating-Point Values. DEST[31:0] = SRC1[31:0] / SRC2[31:0]
224
                builder.Divide(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
×
225
                break;
×
226
            
227
            case Mnemonic.Ret:
228
                // TODO: Verify correctness of operation with Vectors.
229

230
                // On x32, this will require better engineering since ulongs are handled somehow differently (return in 2 registers, I think?)
231
                // The x64 prototype should work.
232
                // Are st* registers even used in il2cpp games?
233

234
                if (context.IsVoid)
×
235
                    builder.Return(instruction.IP);
×
236
                else if (context.Definition?.RawReturnType?.Type is Il2CppTypeEnum.IL2CPP_TYPE_R4 or Il2CppTypeEnum.IL2CPP_TYPE_R8)
×
237
                    builder.Return(instruction.IP, InstructionSetIndependentOperand.MakeRegister("xmm0"));
×
238
                else
239
                    builder.Return(instruction.IP, InstructionSetIndependentOperand.MakeRegister("rax"));
×
240
                break;
×
241
            case Mnemonic.Push:
NEW
242
                operandSize = instruction.Op0Kind == OpKind.Register ? instruction.Op0Register.GetSize() : instruction.MemorySize.GetSize();
×
NEW
243
                builder.ShiftStack(instruction.IP, -operandSize);
×
NEW
244
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeStack(0), ConvertOperand(instruction, 0));
×
UNCOV
245
                break;
×
246
            case Mnemonic.Pop:
NEW
247
                operandSize = instruction.Op0Kind == OpKind.Register ? instruction.Op0Register.GetSize() : instruction.MemorySize.GetSize();
×
NEW
248
                builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeStack(0));
×
NEW
249
                builder.ShiftStack(instruction.IP, operandSize);
×
UNCOV
250
                break;
×
251
            case Mnemonic.Sub:
252
            case Mnemonic.Add:
253
                var isSubtract = instruction.Mnemonic == Mnemonic.Sub;
×
254

255
                // Special case - stack shift
256
                if (instruction.Op0Register == Register.RSP && instruction.Op1Kind.IsImmediate())
×
257
                {
258
                    var amount = (int)instruction.GetImmediate(1);
×
259
                    builder.ShiftStack(instruction.IP, isSubtract ? -amount : amount);
×
260
                    break;
×
261
                }
262

263
                var left = ConvertOperand(instruction, 0);
×
264
                var right = ConvertOperand(instruction, 1);
×
265
                if (isSubtract)
×
266
                    builder.Subtract(instruction.IP, left, left, right);
×
267
                else
268
                    builder.Add(instruction.IP, left, left, right);
×
269

270
                break;
×
271
            case Mnemonic.Addss:
272
            case Mnemonic.Subss:
273
            {
274
                // Addss and subss are just floating point add/sub, but we don't need to handle the stack stuff
275
                // But we do need to handle 2 vs 3 operand forms
276
                InstructionSetIndependentOperand dest;
277
                InstructionSetIndependentOperand src1;
278
                InstructionSetIndependentOperand src2;
279

280
                if (instruction.OpCount == 3)
×
281
                {
282
                    //dest, src1, src2
283
                    dest = ConvertOperand(instruction, 0);
×
284
                    src1 = ConvertOperand(instruction, 1);
×
285
                    src2 = ConvertOperand(instruction, 2);
×
286
                }
287
                else if (instruction.OpCount == 2)
×
288
                {
289
                    //DestAndSrc1, Src2
290
                    dest = ConvertOperand(instruction, 0);
×
291
                    src1 = dest;
×
292
                    src2 = ConvertOperand(instruction, 1);
×
293
                }
294
                else
295
                    goto default;
296

297
                if (instruction.Mnemonic == Mnemonic.Subss)
×
298
                    builder.Subtract(instruction.IP, dest, src1, src2);
×
299
                else
300
                    builder.Add(instruction.IP, dest, src1, src2);
×
301
                break;
×
302
            }
303
            // The following pair of instructions does not update the Carry Flag (CF):
304
            case Mnemonic.Dec:
305
                builder.Subtract(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(1));
×
306
                break;
×
307
            case Mnemonic.Inc:
308
                builder.Add(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(1));
×
309
                break;
×
310

311
            case Mnemonic.Shufps: // Packed Interleave Shuffle of Quadruplets of Single Precision Floating-Point Values
312
            {
313
                if (instruction.Op1Kind == OpKind.Memory)
×
314
                    goto default;
315
                
316
                var imm = instruction.Immediate8;
×
317
                var src1 = X86Utils.GetRegisterName(instruction.Op0Register);
×
318
                var src2 = X86Utils.GetRegisterName(instruction.Op1Register);
×
319
                var dest = "XMM_TEMP";
×
320
                //TEMP_DEST[31:0] := Select4(SRC1[127:0], imm8[1:0]);
321
                builder.Move(instruction.IP, ConvertVector(dest, 0), ConvertVector(src1, imm & 0b11)); 
×
322
                //TEMP_DEST[63:32] := Select4(SRC1[127:0], imm8[3:2]);
323
                builder.Move(instruction.IP, ConvertVector(dest, 1), ConvertVector(src1, (imm >> 2) & 0b11)); 
×
324
                //TEMP_DEST[95:64] := Select4(SRC2[127:0], imm8[5:4]);
325
                builder.Move(instruction.IP, ConvertVector(dest, 2), ConvertVector(src2, (imm >> 4) & 0b11));
×
326
                //TEMP_DEST[127:96] := Select4(SRC2[127:0], imm8[7:6]);
327
                builder.Move(instruction.IP, ConvertVector(dest, 3), ConvertVector(src2, (imm >> 6) & 0b11));
×
328
                // where Select4(regSlice, imm) => regSlice.[imm switch => { 0 => 0..31, 1 => 32..63, 2 => 64..95, 3 => 96...127 }];
329
                builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeRegister(dest)); // DEST = TEMP_DEST
×
330
                break;
×
331

332
                static InstructionSetIndependentOperand ConvertVector(string reg, int imm) =>
333
                    InstructionSetIndependentOperand.MakeVectorElement(reg, IsilVectorRegisterElementOperand.VectorElementWidth.S, imm);
×
334
            }
335
                
336
            case Mnemonic.Unpcklps : // Unpack and Interleave Low Packed Single Precision Floating-Point Values
337
            {
338
                if (instruction.Op1Kind == OpKind.Memory)
×
339
                    goto default;
340
                
341
                var src1 = X86Utils.GetRegisterName(instruction.Op0Register);
×
342
                var src2 = X86Utils.GetRegisterName(instruction.Op1Register);
×
343
                var dest = "XMM_TEMP";
×
344
                builder.Move(instruction.IP, ConvertVector(dest, 0), ConvertVector(src1, 0)); //TMP_DEST[31:0] := SRC1[31:0]
×
345
                builder.Move(instruction.IP, ConvertVector(dest, 1), ConvertVector(src2, 0)); //TMP_DEST[63:32] := SRC2[31:0]
×
346
                builder.Move(instruction.IP, ConvertVector(dest, 2), ConvertVector(src1, 1)); //TMP_DEST[95:64] := SRC1[63:32]
×
347
                builder.Move(instruction.IP, ConvertVector(dest, 3), ConvertVector(src2, 1)); //TMP_DEST[127:96] := SRC2[63:32]
×
348
                builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeRegister(dest)); // DEST = TEMP_DEST
×
349
                break;
×
350

351
                static InstructionSetIndependentOperand ConvertVector(string reg, int imm) =>
352
                    InstructionSetIndependentOperand.MakeVectorElement(reg, IsilVectorRegisterElementOperand.VectorElementWidth.S, imm);
×
353
            }
354
            
355
            case Mnemonic.Call:
356
                // We don't try and resolve which method is being called, but we do need to know how many parameters it has
357
                // I would hope that all of these methods have the same number of arguments, else how can they be inlined?
358

359
                var target = instruction.NearBranchTarget;
×
360

361
                if (context.AppContext.MethodsByAddress.TryGetValue(target, out var possibleMethods))
×
362
                {
363
                    if (possibleMethods.Count == 1)
×
364
                    {
365
                        builder.Call(instruction.IP, target, X64CallingConventionResolver.ResolveForManaged(possibleMethods[0]));
×
366
                    }
367
                    else
368
                    {
369
                        MethodAnalysisContext ctx = null!;
×
370
                        var lpars = -1;
×
371

372
                        // Very naive approach, folds with structs in parameters if GCC is used:
373
                        foreach (var method in possibleMethods)
×
374
                        {
375
                            var pars = method.ParameterCount;
×
376
                            if (method.IsStatic) pars++;
×
377
                            if (pars > lpars)
×
378
                            {
379
                                lpars = pars;
×
380
                                ctx = method;
×
381
                            }
382
                        }
383

384
                        // On post-analysis, you can discard methods according to the registers used, see X64CallingConventionResolver.
385
                        // This is less effective on GCC because MSVC doesn't overlap registers.
386

387
                        builder.Call(instruction.IP, target, X64CallingConventionResolver.ResolveForManaged(ctx));
×
388
                    }
389
                }
390
                else
391
                {
392
                    // This isn't a managed method, so for now we don't know its parameter count.
393
                    // This will need to be rewritten if we ever stumble upon an unmanaged method that accepts more than 4 parameters.
394
                    // These can be converted to dedicated ISIL instructions for specific API functions at a later stage. (by a post-processing step)
395

396
                    builder.Call(instruction.IP, target, X64CallingConventionResolver.ResolveForUnmanaged(context.AppContext, target));
×
397
                }
398

399
                if (callNoReturn)
×
400
                {
401
                    // Our function decided to jump into a thunk or do a funny return.
402
                    // We will insert a return after the call.
403
                    // According to common sense, such callee must have the same return value as the caller, unless it's __noreturn.
404
                    // I hope someone else will catch up on this and figure out non-returning functions.
405

406
                    // TODO: Determine whether a function is an actual thunk and it's *technically better* to duplicate code for it, or if it's a regular retcall.
407
                    // Basic implementation may use context.AppContext.MethodsByAddress, but this doesn't catch thunks only.
408
                    // For example, SWDT often calls gc::GarbageCollector::SetWriteBarrier through a long jmp chain. That's a whole function, not just a thunk.
409

410
                    goto case Mnemonic.Ret;
×
411
                }
412

413
                break;
414
            case Mnemonic.Test:
415
                if (instruction.Op0Kind == OpKind.Register && instruction.Op1Kind == OpKind.Register && instruction.Op0Register == instruction.Op1Register)
×
416
                {
417
                    builder.Compare(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(0));
×
418
                    break;
×
419
                }
420

421
                //Fall through to cmp, as test is just a cmp that doesn't set flags
422
                goto case Mnemonic.Cmp;
423
            case Mnemonic.Cmp:
424
            case Mnemonic.Comiss: //comiss is just a floating point compare dest[31:0] == src[31:0]
425
            case Mnemonic.Ucomiss: // same, but unsigned
426
                builder.Compare(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
427
                break;
×
428
            
429
            case Mnemonic.Cmove: // move if condition
430
            case Mnemonic.Cmovne: 
431
            case Mnemonic.Cmova:
432
            case Mnemonic.Cmovg:
433
            case Mnemonic.Cmovae:
434
            case Mnemonic.Cmovge:
435
            case Mnemonic.Cmovb:
436
            case Mnemonic.Cmovl:
437
            case Mnemonic.Cmovbe:
438
            case Mnemonic.Cmovle: 
439
            case Mnemonic.Cmovs: 
440
            case Mnemonic.Cmovns: 
441
                switch (instruction.Mnemonic)
×
442
                {
443
                    case Mnemonic.Cmove: // equals
444
                        builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1); // skip if not eq
×
445
                        break;
×
446
                    case Mnemonic.Cmovne: // not equals
447
                        builder.JumpIfEqual(instruction.IP, instruction.IP + 1); // skip if eq
×
448
                        break;
×
449
                    case Mnemonic.Cmovs: // sign
450
                        builder.JumpIfNotSign(instruction.IP, instruction.IP + 1); // skip if not sign
×
451
                        break;
×
452
                    case Mnemonic.Cmovns: // not sign
453
                        builder.JumpIfSign(instruction.IP, instruction.IP + 1); // skip if sign
×
454
                        break;
×
455
                    case Mnemonic.Cmova:
456
                    case Mnemonic.Cmovg: // greater
457
                        builder.JumpIfLessOrEqual(instruction.IP, instruction.IP + 1); // skip if not gt
×
458
                        break;
×
459
                    case Mnemonic.Cmovae:
460
                    case Mnemonic.Cmovge: // greater or eq
461
                        builder.JumpIfLess(instruction.IP, instruction.IP + 1); // skip if not gt or eq
×
462
                        break;
×
463
                    case Mnemonic.Cmovb:
464
                    case Mnemonic.Cmovl: // less
465
                        builder.JumpIfGreaterOrEqual(instruction.IP, instruction.IP + 1); // skip if not lt
×
466
                        break;
×
467
                    case Mnemonic.Cmovbe:
468
                    case Mnemonic.Cmovle: // less or eq
469
                        builder.JumpIfGreater(instruction.IP, instruction.IP + 1); // skip if not lt or eq
×
470
                        break;
471
                }
472
                builder.Move(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); // set if cond
×
473
                builder.Nop(instruction.IP + 1);
×
474
                break;
×
475

476
            case Mnemonic.Maxss: // dest < src ? src : dest
477
            case Mnemonic.Minss: // dest > src ? src : dest
478
            {
479
                var dest = ConvertOperand(instruction, 0);
×
480
                var src = ConvertOperand(instruction, 1);
×
481
                builder.Compare(instruction.IP, dest, src); // compare dest & src
×
482
                if (instruction.Mnemonic == Mnemonic.Maxss)
×
483
                    builder.JumpIfGreaterOrEqual(instruction.IP, instruction.IP + 1); // enter if dest < src
×
484
                else
485
                    builder.JumpIfLessOrEqual(instruction.IP, instruction.IP + 1); // enter if dest > src
×
486
                builder.Move(instruction.IP, dest, src); // dest = src
×
487
                builder.Nop(instruction.IP + 1); // exit for IF
×
488
                break;
×
489
            }
490
            
491
            case Mnemonic.Cmpxchg: // compare and exchange
492
            {
493
                var accumulator = InstructionSetIndependentOperand.MakeRegister(instruction.Op1Register.GetSize() switch
×
494
                {
×
495
                    8 => X86Utils.GetRegisterName(Register.RAX),
×
496
                    4 => X86Utils.GetRegisterName(Register.EAX),
×
497
                    2 => X86Utils.GetRegisterName(Register.AX),
×
498
                    1 => X86Utils.GetRegisterName(Register.AL),
×
499
                    _ => throw new NotSupportedException("unexpected behavior")
×
500
                });
×
501
                var dest = ConvertOperand(instruction, 0);
×
502
                var src = ConvertOperand(instruction, 1);
×
503
                builder.Compare(instruction.IP, accumulator, dest);
×
504
                builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1); // if accumulator == dest
×
505
                // SET ZF = 1
506
                builder.Move(instruction.IP, dest, src); // DEST = SRC
×
507
                builder.Goto(instruction.IP, instruction.IP + 2); // END IF
×
508
                // ELSE
509
                // SET ZF = 0
510
                builder.Move(instruction.IP + 1, accumulator, dest); // accumulator = dest
×
511
                
512
                builder.Nop(instruction.IP + 2); // exit for IF
×
513
                break;
×
514
            }
515
            
516
            case Mnemonic.Jmp:
517
                if (instruction.Op0Kind != OpKind.Register)
×
518
                {
519
                    var jumpTarget = instruction.NearBranchTarget;
×
520

521
                    var methodEnd = instruction.IP + (ulong)context.RawBytes.Length;
×
522
                    var methodStart = context.UnderlyingPointer;
×
523

524
                    if (jumpTarget < methodStart || jumpTarget > methodEnd)
×
525
                    {
526
                        callNoReturn = true;
×
527
                        goto case Mnemonic.Call;
×
528
                    }
529
                    else
530
                    {
531
                        builder.Goto(instruction.IP, jumpTarget);
×
532
                        break;
×
533
                    }
534
                }
535
                if (instruction.Op0Kind == OpKind.Register) // ex: jmp rax
×
536
                {
537
                    builder.CallRegister(instruction.IP, ConvertOperand(instruction, 0), noReturn: true);
×
538
                    break;
×
539
                }
540

541
                goto default;
542
            case Mnemonic.Je:
543
                if (instruction.Op0Kind != OpKind.Register)
×
544
                {
545
                    var jumpTarget = instruction.NearBranchTarget;
×
546

547
                    builder.JumpIfEqual(instruction.IP, jumpTarget);
×
548
                    break;
×
549
                }
550

551
                goto default;
552
            case Mnemonic.Jne:
553
                if (instruction.Op0Kind != OpKind.Register)
×
554
                {
555
                    var jumpTarget = instruction.NearBranchTarget;
×
556

557
                    builder.JumpIfNotEqual(instruction.IP, jumpTarget);
×
558
                    break;
×
559
                }
560
                goto default;
561
            case Mnemonic.Js:
562
                if (instruction.Op0Kind != OpKind.Register)
×
563
                {
564
                    var jumpTarget = instruction.NearBranchTarget;
×
565

566
                    builder.JumpIfSign(instruction.IP, jumpTarget);
×
567
                    break;
×
568
                }
569
                
570
                goto default;
571
            case Mnemonic.Jns:
572
                if (instruction.Op0Kind != OpKind.Register)
×
573
                {
574
                    var jumpTarget = instruction.NearBranchTarget;
×
575

576
                    builder.JumpIfNotSign(instruction.IP, jumpTarget);
×
577
                    break;
×
578
                }
579
                
580
                goto default;
581
            case Mnemonic.Jg:
582
            case Mnemonic.Ja:
583
                if (instruction.Op0Kind != OpKind.Register)
×
584
                {
585
                    var jumpTarget = instruction.NearBranchTarget;
×
586

587
                    builder.JumpIfGreater(instruction.IP, jumpTarget);
×
588
                    break;
×
589
                }
590

591
                goto default;
592
            case Mnemonic.Jl:
593
            case Mnemonic.Jb:
594
                if (instruction.Op0Kind != OpKind.Register)
×
595
                {
596
                    var jumpTarget = instruction.NearBranchTarget;
×
597

598
                    builder.JumpIfLess(instruction.IP, jumpTarget);
×
599
                    break;
×
600
                }
601

602
                goto default;
603
            case Mnemonic.Jge:
604
            case Mnemonic.Jae:
605
                if (instruction.Op0Kind != OpKind.Register)
×
606
                {
607
                    var jumpTarget = instruction.NearBranchTarget;
×
608

609
                    builder.JumpIfGreaterOrEqual(instruction.IP, jumpTarget);
×
610
                    break;
×
611
                }
612

613
                goto default;
614
            case Mnemonic.Jle:
615
            case Mnemonic.Jbe:
616
                if (instruction.Op0Kind != OpKind.Register)
×
617
                {
618
                    var jumpTarget = instruction.NearBranchTarget;
×
619

620
                    builder.JumpIfLessOrEqual(instruction.IP, jumpTarget);
×
621
                    break;
×
622
                }
623

624
                goto default;
625
            case Mnemonic.Xchg:
626
                // There was supposed to be a push-mov-pop set but instructionAddress said no
627
                builder.Exchange(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
628
                break;
×
629
            case Mnemonic.Int:
630
            case Mnemonic.Int3:
631
                builder.Interrupt(instruction.IP); // We'll add it but eliminate later, can be used as a hint since compilers only emit it in normally unreachable code or in error handlers
×
632
                break;
×
633
            case Mnemonic.Prefetchw: // Fetches the cache line containing the specified byte from memory to the 1st or 2nd level cache, invalidating other cached copies.
634
            case Mnemonic.Nop:
635
                // While this is literally a nop and there's in theory no point emitting anything for it, it could be used as a jump target.
636
                // So we'll emit an ISIL nop for it.
637
                builder.Nop(instruction.IP);
×
638
                break;
×
639
            default:
640
                builder.NotImplemented(instruction.IP, FormatInstruction(instruction));
×
641
                break;
642
        }
643
    }
×
644

645

646
    private InstructionSetIndependentOperand ConvertOperand(Instruction instruction, int operand)
647
    {
648
        var kind = instruction.GetOpKind(operand);
×
649

650
        if (kind == OpKind.Register)
×
651
            return InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.GetOpRegister(operand)));
×
652
        if (kind.IsImmediate())
×
653
            return InstructionSetIndependentOperand.MakeImmediate(instruction.GetImmediate(operand));
×
654
        if (kind == OpKind.Memory && instruction.MemoryBase == Register.RSP)
×
655
            return InstructionSetIndependentOperand.MakeStack((int)instruction.MemoryDisplacement32);
×
656

657
        //Memory
658
        //Most complex to least complex
659

660
        if (instruction.IsIPRelativeMemoryOperand)
×
661
            return InstructionSetIndependentOperand.MakeMemory(new((long)instruction.IPRelativeMemoryAddress));
×
662

663
        //All four components
664
        if (instruction.MemoryIndex != Register.None && instruction.MemoryBase != Register.None && instruction.MemoryDisplacement64 != 0)
×
665
        {
666
            var mBase = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase));
×
667
            var mIndex = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryIndex));
×
668
            return InstructionSetIndependentOperand.MakeMemory(new(mBase, mIndex, instruction.MemoryDisplacement32, instruction.MemoryIndexScale));
×
669
        }
670

671
        //No addend
672
        if (instruction.MemoryIndex != Register.None && instruction.MemoryBase != Register.None)
×
673
        {
674
            var mBase = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase));
×
675
            var mIndex = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryIndex));
×
676
            return InstructionSetIndependentOperand.MakeMemory(new(mBase, mIndex, instruction.MemoryIndexScale));
×
677
        }
678

679
        //No index (and so no scale)
680
        if (instruction.MemoryBase != Register.None && instruction.MemoryDisplacement64 > 0)
×
681
        {
682
            var mBase = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase));
×
683
            return InstructionSetIndependentOperand.MakeMemory(new(mBase, (long)instruction.MemoryDisplacement64));
×
684
        }
685

686
        //Only base
687
        if (instruction.MemoryBase != Register.None)
×
688
        {
689
            return InstructionSetIndependentOperand.MakeMemory(new(InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase))));
×
690
        }
691

692
        //Only addend
693
        return InstructionSetIndependentOperand.MakeMemory(new((long)instruction.MemoryDisplacement64));
×
694
    }
695
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc