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

SamboyCoding / Cpp2IL / 17989030358

24 Sep 2025 08:39PM UTC coverage: 30.294% (-4.0%) from 34.302%
17989030358

Pull #481

github

web-flow
Merge be900a5ca into 8cf262f50
Pull Request #481: Decompiler

1806 of 7579 branches covered (23.83%)

Branch coverage included in aggregate %.

100 of 1854 new or added lines in 29 files covered. (5.39%)

41 existing lines in 6 files now uncovered.

4084 of 11864 relevant lines covered (34.42%)

165379.78 hits per line

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

0.0
/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using Disarm;
5
using Cpp2IL.Core.Api;
6
using Cpp2IL.Core.Il2CppApiFunctions;
7
using Cpp2IL.Core.ISIL;
8
using Cpp2IL.Core.Model.Contexts;
9
using Cpp2IL.Core.Utils;
10
using Disarm.InternalDisassembly;
11
using LibCpp2IL;
12
using Cpp2IL.Core.Logging;
13

14
namespace Cpp2IL.Core.InstructionSets;
15

16
public class NewArmV8InstructionSet : Cpp2IlInstructionSet
17
{
18
    [ThreadStatic]
19
    private static Dictionary<Arm64Register, ulong> adrpOffsets = new();
×
20

21
    public override Memory<byte> GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator)
22
    {
23
        if (context is not ConcreteGenericMethodAnalysisContext)
×
24
        {
25
            //Managed method or attr gen => grab raw byte range between a and b
26
            var startOfNextFunction = (int)MiscUtils.GetAddressOfNextFunctionStart(context.UnderlyingPointer);
×
27
            var ptrAsInt = (int)context.UnderlyingPointer;
×
28
            var count = startOfNextFunction - ptrAsInt;
×
29

30
            if (startOfNextFunction > 0)
×
31
                return LibCpp2IlMain.Binary!.GetRawBinaryContent().AsMemory(ptrAsInt, count);
×
32
        }
33

34
        var result = NewArm64Utils.GetArm64MethodBodyAtVirtualAddress(context.UnderlyingPointer);
×
35
        var endVa = result.LastValid().Address + 4;
×
36

37
        var start = (int)context.AppContext.Binary.MapVirtualAddressToRaw(context.UnderlyingPointer);
×
38
        var end = (int)context.AppContext.Binary.MapVirtualAddressToRaw(endVa);
×
39

40
        //Sanity check
41
        if (start < 0 || end < 0 || start >= context.AppContext.Binary.RawLength || end >= context.AppContext.Binary.RawLength)
×
42
            throw new Exception($"Failed to map virtual address 0x{context.UnderlyingPointer:X} to raw address for method {context!.DeclaringType?.FullName}/{context.Name} - start: 0x{start:X}, end: 0x{end:X} are out of bounds for length {context.AppContext.Binary.RawLength}.");
×
43

44
        return context.AppContext.Binary.GetRawBinaryContent().AsMemory(start, end - start);
×
45
    }
46

47
    public override List<object> GetParameterOperandsFromMethod(MethodAnalysisContext context)
48
    {
NEW
49
        return [];
×
50
    }
51

52
    public override List<Instruction> GetIsilFromMethod(MethodAnalysisContext context)
53
    {
NEW
54
        var insns = NewArm64Utils.GetArm64MethodBodyAtVirtualAddress(context.UnderlyingPointer);
×
55

56
        if (adrpOffsets == null!) //Null suppress because thread static weirdness
×
57
            adrpOffsets = new();
×
58

59
        adrpOffsets.Clear();
×
60

NEW
61
        var instructions = new List<Instruction>();
×
NEW
62
        var addresses = new List<ulong>();
×
63

UNCOV
64
        foreach (var instruction in insns)
×
NEW
65
            ConvertInstructionStatement(instruction, instructions, addresses, context);
×
66

67
        // fix branches
NEW
68
        for (var i = 0; i < instructions.Count; i++)
×
69
        {
NEW
70
            var instruction = instructions[i];
×
71

NEW
72
            if (instruction.OpCode != OpCode.Jump && instruction.OpCode != OpCode.ConditionalJump)
×
73
                continue;
74

NEW
75
            var targetAddress = (ulong)instruction.Operands[0];
×
NEW
76
            var targetIndex = addresses.FindIndex(addr => addr == targetAddress);
×
77

NEW
78
            if (targetIndex == -1)
×
79
            {
NEW
80
                instruction.OpCode = OpCode.Invalid;
×
NEW
81
                instruction.Operands = [$"Jump target not found in method: 0x{targetAddress:X4}"];
×
NEW
82
                continue;
×
83
            }
84

NEW
85
            var targetInstruction = instructions[targetIndex];
×
86

NEW
87
            instruction.Operands[0] = targetInstruction;
×
88
        }
89

NEW
90
        adrpOffsets.Clear();
×
NEW
91
        return instructions;
×
92
    }
93

94
    private void ConvertInstructionStatement(Arm64Instruction instruction, List<Instruction> instructions, List<ulong> addresses, MethodAnalysisContext context)
95
    {
NEW
96
        var address = instruction.Address;
×
97

98
        void Add(ulong address, OpCode opCode, params object[] operands)
99
        {
NEW
100
            addresses.Add(address);
×
NEW
101
            instructions.Add(new Instruction(instructions.Count, opCode, operands));
×
NEW
102
        }
×
103

UNCOV
104
        switch (instruction.Mnemonic)
×
105
        {
106
            case Arm64Mnemonic.MOV:
107
            case Arm64Mnemonic.MOVZ:
108
            case Arm64Mnemonic.FMOV:
109
            case Arm64Mnemonic.SXTW: // move and sign extend Wn to Xd
110
            case Arm64Mnemonic.LDR:
111
            case Arm64Mnemonic.LDRB:
112
                //Load and move are (dest, src)
113

114
                if (instruction.MemIsPreIndexed) //  such as  X8, [X19,#0x30]! 
×
115
                {
116
                    //Regardless of anything else, we're trashing any possible ADRP offsets in the dest here, so let's clear that
NEW
117
                    if (instruction.Op0Kind == Arm64OperandKind.Register)
×
118
                        adrpOffsets.Remove(instruction.Op0Reg);
×
119

120
                    var operate = ConvertOperand(instruction, 1);
×
NEW
121
                    if (operate is MemoryOperand operand)
×
122
                    {
NEW
123
                        var register = (Register)operand.Base!;
×
124
                        // X19= X19, #0x30
NEW
125
                        Add(address, OpCode.Add, register, register, operand.Addend);
×
126
                        //X8 = [X19]
NEW
127
                        Add(address, OpCode.Move, ConvertOperand(instruction, 0), new MemoryOperand(new Register(null, register.ToString()!.ToUpperInvariant())));
×
128
                        break;
×
129
                    }
130
                }
131

132
                if (instruction.Op1Kind == Arm64OperandKind.Memory && adrpOffsets.TryGetValue(instruction.MemBase, out var page) && instruction.MemOffset != 0 && instruction.MemAddendReg == Arm64Register.INVALID)
×
133
                {
134
                    //Maybe this is a bit hacky? But I really don't want to write paged load handling into ISIL itself, it's an Arm64 quirk
135
                    //LDR X0, [X1, #0x1000], where X1 was previously loaded with a page address via an ADRP instruction
136
                    //We just return the final address, it makes ISIL happier.
137
                    //TODO check if this is correct
NEW
138
                    var offset = instruction.MemOffset + (long)page;
×
139

140
                    //We're also trashing any possible ADRP offsets in the dest here, so let's clear that now we've possibly grabbed the value if we need it (it's common to store the page and final address in the same register)
NEW
141
                    if (instruction.Op0Kind == Arm64OperandKind.Register)
×
142
                        adrpOffsets.Remove(instruction.Op0Reg);
×
143

NEW
144
                    Add(address, OpCode.Move, ConvertOperand(instruction, 0), new MemoryOperand(addend: offset));
×
UNCOV
145
                    break;
×
146
                }
147

148
                //And again here we're trashing any possible ADRP offsets in the dest here, so let's clear that
NEW
149
                if (instruction.Op0Kind == Arm64OperandKind.Register)
×
150
                    adrpOffsets.Remove(instruction.Op0Reg);
×
151

NEW
152
                Add(address, OpCode.Move, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
NEW
153
                Add(address, OpCode.CheckEqual, new Register(null, "Z"), ConvertOperand(instruction, 0), 0);
×
UNCOV
154
                break;
×
155
            case Arm64Mnemonic.MOVN:
156
                {
157
                    // dest = ~src
158

159
                    //See above re: ADRP offsets
NEW
160
                    if (instruction.Op0Kind == Arm64OperandKind.Register)
×
NEW
161
                        adrpOffsets.Remove(instruction.Op0Reg);
×
162

NEW
163
                    var temp2 = new Register(null, "TEMP");
×
NEW
164
                    Add(address, OpCode.Move, temp2, ConvertOperand(instruction, 1));
×
NEW
165
                    Add(address, OpCode.Not, temp2, temp2);
×
NEW
166
                    Add(address, OpCode.Move, ConvertOperand(instruction, 0), temp2);
×
NEW
167
                    break;
×
168
                }
169
            case Arm64Mnemonic.STR:
170
            case Arm64Mnemonic.STUR: // unscaled
171
            case Arm64Mnemonic.STRB:
172
                //Store is (src, dest)
NEW
173
                Add(address, OpCode.Move, ConvertOperand(instruction, 1), ConvertOperand(instruction, 0));
×
174
                break;
×
175
            case Arm64Mnemonic.STP:
176
                // store pair of registers (reg1, reg2, dest)
177
                {
NEW
178
                    var dest3 = ConvertOperand(instruction, 2);
×
NEW
179
                    if (dest3 is Register { Name: "X31" }) // if stack
×
180
                    {
NEW
181
                        Add(address, OpCode.Move, dest3, ConvertOperand(instruction, 0));
×
NEW
182
                        Add(address, OpCode.Move, dest3, ConvertOperand(instruction, 1));
×
183
                    }
NEW
184
                    else if (dest3 is MemoryOperand memory)
×
185
                    {
NEW
186
                        var firstRegister = ConvertOperand(instruction, 0);
×
NEW
187
                        long size = ((Register)firstRegister).Name[0] == 'W' ? 4 : 8;
×
NEW
188
                        Add(address, OpCode.Move, dest3, firstRegister); // [REG + offset] = REG1
×
NEW
189
                        memory = new MemoryOperand((Register)memory.Base!, addend: memory.Addend + size);
×
NEW
190
                        dest3 = memory;
×
NEW
191
                        Add(address, OpCode.Move, dest3, ConvertOperand(instruction, 1)); // [REG + offset + size] = REG2
×
192
                    }
193
                    else // reg pointer
194
                    {
NEW
195
                        var firstRegister = ConvertOperand(instruction, 0);
×
NEW
196
                        long size = ((Register)firstRegister).Name[0] == 'W' ? 4 : 8;
×
NEW
197
                        Add(address, OpCode.Move, dest3, firstRegister);
×
NEW
198
                        Add(address, OpCode.Add, dest3, dest3, size);
×
NEW
199
                        Add(address, OpCode.Move, dest3, ConvertOperand(instruction, 1));
×
200
                    }
201
                }
UNCOV
202
                break;
×
203
            case Arm64Mnemonic.ADRP:
204
                //Just handle as a move
NEW
205
                Add(address, OpCode.Move, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
206
                adrpOffsets[instruction.Op0Reg] = (ulong)instruction.Op1Imm;
×
207
                break;
×
208
            case Arm64Mnemonic.LDP when instruction.Op2Kind == Arm64OperandKind.Memory:
×
209
                //LDP (dest1, dest2, [mem]) - basically just treat as two loads, with the second offset by the length of the first
210
                var destRegSize = instruction.Op0Reg switch
×
211
                {
×
212
                    //vector (128 bit)
×
213
                    >= Arm64Register.V0 and <= Arm64Register.V31 => 16, //TODO check if this is accurate
×
214
                    //double
×
215
                    >= Arm64Register.D0 and <= Arm64Register.D31 => 8,
×
216
                    //single
×
217
                    >= Arm64Register.S0 and <= Arm64Register.S31 => 4,
×
218
                    //half
×
219
                    >= Arm64Register.H0 and <= Arm64Register.H31 => 2,
×
220
                    //word
×
221
                    >= Arm64Register.W0 and <= Arm64Register.W31 => 4,
×
222
                    //x
×
223
                    >= Arm64Register.X0 and <= Arm64Register.X31 => 8,
×
224
                    _ => throw new($"Unknown register size for LDP: {instruction.Op0Reg}")
×
225
                };
×
226

227
                var dest1 = ConvertOperand(instruction, 0);
×
228
                var dest2 = ConvertOperand(instruction, 1);
×
229
                var mem = ConvertOperand(instruction, 2);
×
230

231
                //TODO clean this mess up
NEW
232
                var memInternal = mem as MemoryOperand?;
×
NEW
233
                var mem2 = new MemoryOperand((Register)memInternal!.Value.Base!, addend: memInternal.Value.Addend + destRegSize);
×
234

NEW
235
                Add(address, OpCode.Move, dest1, mem);
×
NEW
236
                Add(address, OpCode.Move, dest2, mem2);
×
UNCOV
237
                break;
×
238
            case Arm64Mnemonic.BL:
NEW
239
                Add(address, OpCode.Call, instruction.BranchTarget, GetArgumentOperandsForCall(context, instruction.BranchTarget).ToArray());
×
240
                break;
×
241
            case Arm64Mnemonic.RET:
NEW
242
                var returnRegister = GetReturnRegisterForContext(context);
×
NEW
243
                if (returnRegister == null)
×
NEW
244
                    Add(address, OpCode.Return);
×
245
                else
NEW
246
                    Add(address, OpCode.Return, returnRegister);
×
UNCOV
247
                break;
×
248
            case Arm64Mnemonic.B:
249
                var target = instruction.BranchTarget;
×
250

251
                if (target < context.UnderlyingPointer || target > context.UnderlyingPointer + (ulong)context.RawBytes.Length)
×
252
                {
253
                    //Unconditional branch to outside the method, treat as call (tail-call, specifically) followed by return
254

NEW
255
                    Add(address, OpCode.Call, instruction.BranchTarget, GetArgumentOperandsForCall(context, instruction.BranchTarget).ToArray());
×
NEW
256
                    var returnRegister2 = GetReturnRegisterForContext(context);
×
NEW
257
                    if (returnRegister2 == null)
×
NEW
258
                        Add(address, OpCode.Return);
×
259
                    else
NEW
260
                        Add(address, OpCode.Return, returnRegister2);
×
261
                }
262
                else
263
                {
NEW
264
                    Add(address, OpCode.Jump, instruction.BranchTarget);
×
265
                }
266

267
                break;
×
268
            case Arm64Mnemonic.BR:
269
                // branches unconditionally to an address in a register, with a hint that this is not a subroutine return.
NEW
270
                Add(address, OpCode.IndirectCall, ConvertOperand(instruction, 0));
×
271
                break;
×
272
            case Arm64Mnemonic.CBNZ:
273
            case Arm64Mnemonic.CBZ:
274
                {
275
                    //Compare and branch if (non-)zero
NEW
276
                    var targetAddr = (ulong)((long)instruction.Address + instruction.Op1Imm);
×
277

278
                    //Compare to zero...
NEW
279
                    Add(address, OpCode.CheckEqual, new Register(null, "Z"), ConvertOperand(instruction, 0), 0);
×
280

281
                    //And jump if (not) equal
NEW
282
                    if (instruction.Mnemonic == Arm64Mnemonic.CBZ)
×
283
                    {
NEW
284
                        Add(address, OpCode.ConditionalJump, targetAddr, new Register(null, "Z"));
×
285
                    }
286
                    else
287
                    {
NEW
288
                        Add(address, OpCode.Not, new Register(null, "TEMP"), new Register(null, "Z"));
×
NEW
289
                        Add(address, OpCode.ConditionalJump, targetAddr, new Register(null, "TEMP"));
×
290
                    }
291
                }
UNCOV
292
                break;
×
293

294
            case Arm64Mnemonic.CMP:
NEW
295
                var op1 = ConvertOperand(instruction, 0);
×
NEW
296
                var op2 = ConvertOperand(instruction, 1);
×
NEW
297
                var temp = new Register(null, "TEMP");
×
298

NEW
299
                Add(address, OpCode.Subtract, temp, op1, op2);
×
NEW
300
                Add(address, OpCode.CheckEqual, new Register(null, "Z"), temp, 0);
×
NEW
301
                break;
×
302

303
            case Arm64Mnemonic.TBNZ:
304
            // TBNZ R<t>, #imm, label
305
            // test bit and branch if NonZero
306
            case Arm64Mnemonic.TBZ:
307
                // TBZ R<t>, #imm, label
308
                // test bit and branch if Zero
309
                {
NEW
310
                    var targetAddr = (ulong)((long)instruction.Address + instruction.Op2Imm);
×
NEW
311
                    var bit = 1 << (int)instruction.Op1Imm;
×
NEW
312
                    var temp2 = new Register(null, "TEMP");
×
NEW
313
                    var src = ConvertOperand(instruction, 0);
×
NEW
314
                    Add(address, OpCode.Move, temp2, src); // temp = src
×
NEW
315
                    Add(address, OpCode.Move, temp2, bit); // temp = temp & bit
×
NEW
316
                    Add(address, OpCode.Move, temp2, bit); // result = temp == bit
×
NEW
317
                    if (instruction.Mnemonic == Arm64Mnemonic.TBNZ)
×
318
                    {
NEW
319
                        Add(address, OpCode.ConditionalJump, targetAddr, new Register(null, "Z")); // if (result) goto targetAddr
×
320
                    }
321
                    else
322
                    {
NEW
323
                        Add(address, OpCode.Not, new Register(null, "TEMP"), new Register(null, "Z"));
×
NEW
324
                        Add(address, OpCode.ConditionalJump, targetAddr, new Register(null, "TEMP")); // if (result) goto targetAddr
×
325
                    }
326
                }
UNCOV
327
                break;
×
328
            case Arm64Mnemonic.UBFM:
329
                // UBFM dest, src, #<immr>, #<imms>
330
                // dest = (src >> #<immr>) & ((1 << #<imms>) - 1)
331
                {
NEW
332
                    var dest3 = ConvertOperand(instruction, 0);
×
NEW
333
                    Add(address, OpCode.Move, dest3, ConvertOperand(instruction, 1)); // dest = src
×
NEW
334
                    Add(address, OpCode.ShiftRight, dest3, dest3, ConvertOperand(instruction, 2)); // dest = dest >> #<immr>
×
NEW
335
                    var imms = (int)instruction.Op3Imm;
×
NEW
336
                    Add(address, OpCode.And, dest3, dest3, (1 << imms) - 1); // dest = dest & constexpr { ((1 << #<imms>) - 1) }
×
337
                }
UNCOV
338
                break;
×
339

340
            case Arm64Mnemonic.MUL:
341
            case Arm64Mnemonic.FMUL:
342
                //Multiply is (dest, src1, src2)
NEW
343
                Add(address, OpCode.Multiply, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
×
344
                break;
×
345

346
            case Arm64Mnemonic.ADD:
347
            case Arm64Mnemonic.FADD:
348
                //Add is (dest, src1, src2)
NEW
349
                Add(address, OpCode.Add, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
×
350
                break;
×
351

352
            case Arm64Mnemonic.SUB:
353
            case Arm64Mnemonic.FSUB:
354
                //Sub is (dest, src1, src2)
NEW
355
                Add(address, OpCode.Subtract, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
×
356
                break;
×
357

358
            case Arm64Mnemonic.AND:
359
                //And is (dest, src1, src2)
NEW
360
                Add(address, OpCode.And, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
×
NEW
361
                break;
×
362

363
            case Arm64Mnemonic.ADDS:
364
            case Arm64Mnemonic.SUBS:
365
            case Arm64Mnemonic.ANDS:
NEW
366
                var dest = ConvertOperand(instruction, 0);
×
NEW
367
                var src1 = ConvertOperand(instruction, 1);
×
NEW
368
                var src2 = ConvertOperand(instruction, 2);
×
369

NEW
370
                var opCode = instruction.Mnemonic switch
×
NEW
371
                {
×
NEW
372
                    Arm64Mnemonic.ADDS => OpCode.Add,
×
NEW
373
                    Arm64Mnemonic.SUBS => OpCode.Subtract,
×
NEW
374
                    Arm64Mnemonic.ANDS => OpCode.And,
×
NEW
375
                    _ => OpCode.Invalid
×
NEW
376
                };
×
377

NEW
378
                Add(address, opCode, dest, src1, src2);
×
NEW
379
                Add(address, OpCode.CheckEqual, new Register(null, "Z"), dest, 0);
×
UNCOV
380
                break;
×
381

382
            case Arm64Mnemonic.ORR:
383
                //Orr is (dest, src1, src2)
NEW
384
                Add(address, OpCode.Or, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
×
385
                break;
×
386

387
            case Arm64Mnemonic.EOR:
388
                //Eor (aka xor) is (dest, src1, src2)
NEW
389
                Add(address, OpCode.Xor, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
×
390
                break;
×
391

392
            default:
NEW
393
                Add(address, OpCode.NotImplemented, $"Instruction {instruction.Mnemonic} not yet implemented.");
×
394
                break;
395
        }
396
    }
×
397

398
    private object ConvertOperand(Arm64Instruction instruction, int operand)
399
    {
400
        var kind = operand switch
×
401
        {
×
402
            0 => instruction.Op0Kind,
×
403
            1 => instruction.Op1Kind,
×
404
            2 => instruction.Op2Kind,
×
405
            3 => instruction.Op3Kind,
×
406
            _ => throw new ArgumentOutOfRangeException(nameof(operand), $"Operand must be between 0 and 3, inclusive. Got {operand}")
×
407
        };
×
408

409
        if (kind is Arm64OperandKind.Immediate or Arm64OperandKind.ImmediatePcRelative)
×
410
        {
411
            var imm = operand switch
×
412
            {
×
413
                0 => instruction.Op0Imm,
×
414
                1 => instruction.Op1Imm,
×
415
                2 => instruction.Op2Imm,
×
416
                3 => instruction.Op3Imm,
×
417
                _ => throw new ArgumentOutOfRangeException(nameof(operand), $"Operand must be between 0 and 3, inclusive. Got {operand}")
×
418
            };
×
419

420
            if (kind == Arm64OperandKind.ImmediatePcRelative)
×
421
                imm += (long)instruction.Address + 4; //Add 4 to the address to get the address of the next instruction (PC-relative addressing is relative to the address of the next instruction, not the current one
×
422

NEW
423
            return imm;
×
424
        }
425

426
        if (kind == Arm64OperandKind.FloatingPointImmediate)
×
427
        {
428
            var imm = operand switch
×
429
            {
×
430
                0 => instruction.Op0FpImm,
×
431
                1 => instruction.Op1FpImm,
×
432
                2 => instruction.Op2FpImm,
×
433
                3 => instruction.Op3FpImm,
×
434
                _ => throw new ArgumentOutOfRangeException(nameof(operand), $"Operand must be between 0 and 3, inclusive. Got {operand}")
×
435
            };
×
436

NEW
437
            return imm;
×
438
        }
439

440
        if (kind == Arm64OperandKind.Register)
×
441
        {
442
            var reg = operand switch
×
443
            {
×
444
                0 => instruction.Op0Reg,
×
445
                1 => instruction.Op1Reg,
×
446
                2 => instruction.Op2Reg,
×
447
                3 => instruction.Op3Reg,
×
448
                _ => throw new ArgumentOutOfRangeException(nameof(operand), $"Operand must be between 0 and 3, inclusive. Got {operand}")
×
449
            };
×
450

NEW
451
            return new Register(null, reg.ToString().ToUpperInvariant());
×
452
        }
453

454
        if (kind == Arm64OperandKind.Memory)
×
455
        {
456
            var reg = instruction.MemBase;
×
457
            var offset = instruction.MemOffset;
×
458
            var isPreIndexed = instruction.MemIsPreIndexed;
×
459

460
            if (reg == Arm64Register.INVALID)
×
461
                //Offset only
NEW
462
                return new MemoryOperand(addend: offset);
×
463

464
            //TODO Handle more stuff here
NEW
465
            return new MemoryOperand(new Register(null, reg.ToString().ToUpperInvariant()), addend: offset);
×
466
        }
467

468
        if (kind == Arm64OperandKind.VectorRegisterElement)
×
469
        {
470
            var reg = operand switch
×
471
            {
×
472
                0 => instruction.Op0Reg,
×
473
                1 => instruction.Op1Reg,
×
474
                2 => instruction.Op2Reg,
×
475
                3 => instruction.Op3Reg,
×
476
                _ => throw new ArgumentOutOfRangeException(nameof(operand), $"Operand must be between 0 and 3, inclusive. Got {operand}")
×
477
            };
×
478

479
            var vectorElement = operand switch
×
480
            {
×
481
                0 => instruction.Op0VectorElement,
×
482
                1 => instruction.Op1VectorElement,
×
483
                2 => instruction.Op2VectorElement,
×
484
                3 => instruction.Op3VectorElement,
×
485
                _ => throw new ArgumentOutOfRangeException(nameof(operand), $"Operand must be between 0 and 3, inclusive. Got {operand}")
×
486
            };
×
487

488
            var width = vectorElement.Width switch
×
489
            {
×
NEW
490
                Arm64VectorElementWidth.B => "B",
×
NEW
491
                Arm64VectorElementWidth.H => "H",
×
NEW
492
                Arm64VectorElementWidth.S => "S",
×
NEW
493
                Arm64VectorElementWidth.D => "D",
×
494
                _ => throw new ArgumentOutOfRangeException(nameof(vectorElement.Width), $"Unknown vector element width {vectorElement.Width}")
×
495
            };
×
496

NEW
497
            var name = $"{reg.ToString().ToUpperInvariant()}.{width}{vectorElement.Index}";
×
NEW
498
            return new Register(null, name);
×
499
        }
500

NEW
501
        return $"<UNIMPLEMENTED OPERAND TYPE {kind}>";
×
502
    }
503

504
    public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance() => new NewArm64KeyFunctionAddresses();
×
505

506
    public override string PrintAssembly(MethodAnalysisContext context) => context.RawBytes.Span.Length <= 0 ? "" : string.Join("\n", Disassembler.Disassemble(context.RawBytes.Span, context.UnderlyingPointer, new Disassembler.Options(true, true, false)).ToList());
×
507

508
    private object? GetReturnRegisterForContext(MethodAnalysisContext context)
509
    {
510
        var returnType = context.ReturnType;
×
511
        if (returnType.Namespace == nameof(System))
×
512
        {
513
            return returnType.Name switch
×
514
            {
×
515
                "Void" => null, //Void is no return
×
NEW
516
                "Double" => new Register(null, nameof(Arm64Register.V0)), //Builtin double is v0
×
NEW
517
                "Single" => new Register(null, nameof(Arm64Register.V0)), //Builtin float is v0
×
NEW
518
                _ => new Register(null, nameof(Arm64Register.X0)), //All other system types are x0 like any other pointer
×
UNCOV
519
            };
×
520
        }
521

522
        //TODO Do certain value types have different return registers?
523

524
        //Any user type is returned in x0
NEW
525
        return new Register(null, nameof(Arm64Register.X0));
×
526
    }
527

528
    private List<object> GetArgumentOperandsForCall(MethodAnalysisContext contextBeingAnalyzed, ulong callAddr)
529
    {
530
        if (!contextBeingAnalyzed.AppContext.MethodsByAddress.TryGetValue(callAddr, out var methodsAtAddress))
×
531
            //TODO
532
            return [];
×
533

534
        //For the sake of arguments, all we care about is the first method at the address, because they'll only be shared if they have the same signature.
535
        var contextBeingCalled = methodsAtAddress.First();
×
536

537
        var vectorCount = 0;
×
538
        var nonVectorCount = 0;
×
539

NEW
540
        var ret = new List<object>();
×
541

542
        //Handle 'this' if it's an instance method
543
        if (!contextBeingCalled.IsStatic)
×
544
        {
NEW
545
            ret.Add(new Register(null, nameof(Arm64Register.X0)));
×
546
            nonVectorCount++;
×
547
        }
548

549
        foreach (var parameter in contextBeingCalled.Parameters)
×
550
        {
551
            var paramType = parameter.ParameterType;
×
552
            if (paramType.Namespace == nameof(System))
×
553
            {
554
                switch (paramType.Name)
×
555
                {
556
                    case "Single":
557
                    case "Double":
NEW
558
                        ret.Add(new Register(null, (Arm64Register.V0 + vectorCount++).ToString().ToUpperInvariant()));
×
559
                        break;
×
560
                    default:
NEW
561
                        ret.Add(new Register(null, (Arm64Register.X0 + nonVectorCount++).ToString().ToUpperInvariant()));
×
562
                        break;
×
563
                }
564
            }
565
            else
566
            {
NEW
567
                ret.Add(new Register(null, (Arm64Register.X0 + nonVectorCount++).ToString().ToUpperInvariant()));
×
568
            }
569
        }
570

571
        return ret;
×
572
    }
573
}
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