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

SamboyCoding / Cpp2IL / 15172218253

21 May 2025 08:44PM UTC coverage: 34.282% (+0.2%) from 34.047%
15172218253

Pull #462

github

web-flow
Merge 9de1a7abb into 5807d2b6c
Pull Request #462: Support overriding member types

1801 of 6650 branches covered (27.08%)

Branch coverage included in aggregate %.

127 of 226 new or added lines in 35 files covered. (56.19%)

22 existing lines in 6 files now uncovered.

4199 of 10852 relevant lines covered (38.69%)

186271.78 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);
723,126✔
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))
×
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:
242
                operandSize = instruction.Op0Kind == OpKind.Register ? instruction.Op0Register.GetSize() : instruction.MemorySize.GetSize();
×
243
                builder.ShiftStack(instruction.IP, -operandSize);
×
244
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeStack(0), ConvertOperand(instruction, 0));
×
245
                break;
×
246
            case Mnemonic.Pop:
247
                operandSize = instruction.Op0Kind == OpKind.Register ? instruction.Op0Register.GetSize() : instruction.MemorySize.GetSize();
×
248
                builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeStack(0));
×
249
                builder.ShiftStack(instruction.IP, operandSize);
×
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 (instruction.Op0Kind == OpKind.Register)
×
362
                {
363
                    builder.CallRegister(instruction.IP, ConvertOperand(instruction, 0));
×
364
                }
365
                else if (context.AppContext.MethodsByAddress.TryGetValue(target, out var possibleMethods))
×
366
                {
367
                    if (possibleMethods.Count == 1)
×
368
                    {
369
                        builder.Call(instruction.IP, target, X64CallingConventionResolver.ResolveForManaged(possibleMethods[0]));
×
370
                    }
371
                    else
372
                    {
373
                        MethodAnalysisContext ctx = null!;
×
374
                        var lpars = -1;
×
375

376
                        // Very naive approach, folds with structs in parameters if GCC is used:
377
                        foreach (var method in possibleMethods)
×
378
                        {
NEW
379
                            var pars = method.Parameters.Count;
×
380
                            if (method.IsStatic) pars++;
×
381
                            if (pars > lpars)
×
382
                            {
383
                                lpars = pars;
×
384
                                ctx = method;
×
385
                            }
386
                        }
387

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

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

400
                    builder.Call(instruction.IP, target, X64CallingConventionResolver.ResolveForUnmanaged(context.AppContext, target));
×
401
                }
402

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

410
                    // 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.
411
                    // Basic implementation may use context.AppContext.MethodsByAddress, but this doesn't catch thunks only.
412
                    // For example, SWDT often calls gc::GarbageCollector::SetWriteBarrier through a long jmp chain. That's a whole function, not just a thunk.
413

414
                    goto case Mnemonic.Ret;
×
415
                }
416

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

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

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

525
                    var methodEnd = instruction.IP + (ulong)context.RawBytes.Length;
×
526
                    var methodStart = context.UnderlyingPointer;
×
527

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

545
                goto default;
546
            case Mnemonic.Je:
547
                if (instruction.Op0Kind != OpKind.Register)
×
548
                {
549
                    var jumpTarget = instruction.NearBranchTarget;
×
550

551
                    builder.JumpIfEqual(instruction.IP, jumpTarget);
×
552
                    break;
×
553
                }
554

555
                goto default;
556
            case Mnemonic.Jne:
557
                if (instruction.Op0Kind != OpKind.Register)
×
558
                {
559
                    var jumpTarget = instruction.NearBranchTarget;
×
560

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

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

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

591
                    builder.JumpIfGreater(instruction.IP, jumpTarget);
×
592
                    break;
×
593
                }
594

595
                goto default;
596
            case Mnemonic.Jl:
597
            case Mnemonic.Jb:
598
                if (instruction.Op0Kind != OpKind.Register)
×
599
                {
600
                    var jumpTarget = instruction.NearBranchTarget;
×
601

602
                    builder.JumpIfLess(instruction.IP, jumpTarget);
×
603
                    break;
×
604
                }
605

606
                goto default;
607
            case Mnemonic.Jge:
608
            case Mnemonic.Jae:
609
                if (instruction.Op0Kind != OpKind.Register)
×
610
                {
611
                    var jumpTarget = instruction.NearBranchTarget;
×
612

613
                    builder.JumpIfGreaterOrEqual(instruction.IP, jumpTarget);
×
614
                    break;
×
615
                }
616

617
                goto default;
618
            case Mnemonic.Jle:
619
            case Mnemonic.Jbe:
620
                if (instruction.Op0Kind != OpKind.Register)
×
621
                {
622
                    var jumpTarget = instruction.NearBranchTarget;
×
623

624
                    builder.JumpIfLessOrEqual(instruction.IP, jumpTarget);
×
625
                    break;
×
626
                }
627

628
                goto default;
629
            case Mnemonic.Xchg:
630
                // There was supposed to be a push-mov-pop set but instructionAddress said no
631
                builder.Exchange(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
632
                break;
×
633
            case Mnemonic.Int:
634
            case Mnemonic.Int3:
635
                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
×
636
                break;
×
637
            case Mnemonic.Prefetchw: // Fetches the cache line containing the specified byte from memory to the 1st or 2nd level cache, invalidating other cached copies.
638
            case Mnemonic.Nop:
639
                // 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.
640
                // So we'll emit an ISIL nop for it.
641
                builder.Nop(instruction.IP);
×
642
                break;
×
643
            default:
644
                builder.NotImplemented(instruction.IP, FormatInstruction(instruction));
×
645
                break;
646
        }
647
    }
×
648

649

650
    private InstructionSetIndependentOperand ConvertOperand(Instruction instruction, int operand)
651
    {
652
        var kind = instruction.GetOpKind(operand);
×
653

654
        if (kind == OpKind.Register)
×
655
            return InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.GetOpRegister(operand)));
×
656
        if (kind.IsImmediate())
×
657
            return InstructionSetIndependentOperand.MakeImmediate(instruction.GetImmediate(operand));
×
658
        if (kind == OpKind.Memory && instruction.MemoryBase == Register.RSP)
×
659
            return InstructionSetIndependentOperand.MakeStack((int)instruction.MemoryDisplacement32);
×
660

661
        //Memory
662
        //Most complex to least complex
663

664
        if (instruction.IsIPRelativeMemoryOperand)
×
665
            return InstructionSetIndependentOperand.MakeMemory(new((long)instruction.IPRelativeMemoryAddress));
×
666

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

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

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

690
        //Only base
691
        if (instruction.MemoryBase != Register.None)
×
692
        {
693
            return InstructionSetIndependentOperand.MakeMemory(new(InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase))));
×
694
        }
695

696
        //Only addend
697
        return InstructionSetIndependentOperand.MakeMemory(new((long)instruction.MemoryDisplacement64));
×
698
    }
699
}
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