Beispiel #1
0
        static unsafe void ReadEtlFile(string etlPath, IEnumerable <string> pdbWhitelist, out ProfilerTrace profTrace, Allocator allocator)
        {
            var monoFunctions     = new Dictionary <IntPtr, MonoJitInfo>();
            var discoveredModules = DiscoveredData <DiscoveredModule> .Make(DiscoveredModule.Invalid);

            var discoveredStackFrames = DiscoveredData <CallStackIndex> .Make(CallStackIndex.Invalid);

            var discoveredFunctions = DiscoveredData <DiscoveredFunction> .Make(DiscoveredFunction.Invalid);

            var discoveredThreads = DiscoveredData <ThreadIndex> .Make(ThreadIndex.Invalid);

            string additionalSymbolPath = BurstPath;

#if UNITY_EDITOR
            additionalSymbolPath += ";" + Path.GetDirectoryName(EditorApplication.applicationPath);
#endif

            var options = new TraceLogOptions()
            {
                AlwaysResolveSymbols = true,
                LocalSymbolsOnly     = false,
                AllowUnsafeSymbols   = true,
                AdditionalSymbolPath = additionalSymbolPath,
                ShouldResolveSymbols = path =>
                {
                    path = path.ToLowerInvariant();
                    return(pdbWhitelist.Any(x => path.EndsWith(x)));
                }
            };

            profTrace = new ProfilerTrace();
            using (var trace = TraceLog.OpenOrConvert(etlPath, options))
            {
                var processIndex = FindUnityProcessIndex(trace.Processes);
                var processId    = trace.Processes[processIndex].ProcessID;

                profTrace.Header = new ProfilerTraceHeader
                {
                    SamplingInterval = trace.SampleProfileInterval.TotalMilliseconds,
                    SessionStart     = trace.SessionStartTime.Ticks,
                    SessionEnd       = trace.SessionEndTime.Ticks,
                };

                var sampleRange = new Dictionary <MethodIndex, ulong>();
                using (var samples = new NativeList <SampleData>(Allocator.Temp))
                {
                    SampleData sampleData = default;
                    foreach (var evt in trace.Events)
                    {
                        var sample = evt as SampledProfileTraceData;
                        if (sample == null || sample.ProcessID != processId || sample.NonProcess)
                        {
                            continue;
                        }
                        // TODO sample has a count field that we are currently ignoring. should we?
                        sampleData.Address    = (long)sample.InstructionPointer;
                        sampleData.ThreadIdx  = discoveredThreads.AddData(sample.Thread()?.ThreadIndex ?? ThreadIndex.Invalid);
                        sampleData.TimeStamp  = sample.TimeStampRelativeMSec;
                        sampleData.StackTrace = discoveredStackFrames.AddData(sample.CallStackIndex());
                        var codeAddress = sample.IntructionPointerCodeAddress();
                        sampleData.Function = GetFunctionIndex(codeAddress);
                        samples.Add(sampleData);

                        if (sampleData.Function >= 0)
                        {
                            var index = discoveredFunctions.Data[sampleData.Function].Index;
                            if (index == MethodIndex.Invalid)
                            {
                                continue;
                            }
                            if (!sampleRange.TryGetValue(index, out ulong previousMax))
                            {
                                sampleRange[index] = sample.InstructionPointer;
                            }
                            else
                            {
                                sampleRange[index] = sample.InstructionPointer > previousMax
                                    ? sample.InstructionPointer
                                    : previousMax;
                            }
                        }
                    }
                    profTrace.Samples = samples.ToArray(allocator);
                }

                using (var stackFrames = new NativeList <StackFrameData>(Allocator.Temp))
                {
                    StackFrameData stackTraceData = default;
                    // N.B. this loop adds more stack frames as it executes
                    for (int idx = 0; idx < discoveredStackFrames.Count; idx++)
                    {
                        var stack = trace.CallStacks[discoveredStackFrames.Data[idx]];
                        stackTraceData.Address          = (long)stack.CodeAddress.Address;
                        stackTraceData.Depth            = stack.Depth;
                        stackTraceData.CallerStackFrame = discoveredStackFrames.AddData(stack.Caller?.CallStackIndex ?? CallStackIndex.Invalid);
                        stackTraceData.Function         = GetFunctionIndex(stack.CodeAddress);
                        stackFrames.Add(stackTraceData);
                    }
                    profTrace.StackFrames = stackFrames.ToArray(allocator);
                }

                using (var functions = new NativeList <FunctionData>(Allocator.Temp))
                {
                    var burstModules = new Dictionary <int, bool>
                    {
                        { -1, false }
                    };
                    var          blobData = new NativeList <byte>(Allocator.Temp);
                    FunctionData funcData = default;
                    foreach (var func in discoveredFunctions.Data)
                    {
                        bool copyCode;
                        if (func.Index != MethodIndex.Invalid)
                        {
                            var method = trace.CodeAddresses.Methods[func.Index];
                            if (method.MethodRva > 0 && method.MethodModuleFile != null)
                            {
                                funcData.BaseAddress = method.MethodModuleFile.ImageBase + (ulong)method.MethodRva;
                            }
                            else
                            {
                                funcData.BaseAddress = 0;
                            }
                            if (funcData.BaseAddress > 0 && method.MethodIndex != MethodIndex.Invalid && sampleRange.TryGetValue(method.MethodIndex, out var maxAddress))
                            {
                                Debug.Assert(maxAddress >= funcData.BaseAddress);
                                // use the value of the furthest sample to estimate the length of the function
                                funcData.Length = 16 + (int)(maxAddress - funcData.BaseAddress);
                            }
                            else
                            {
                                funcData.Length = 0;
                            }
                            funcData.Module = discoveredModules.AddData(DiscoveredModule.FromIndex(method.MethodModuleFileIndex));
                            funcData.Name.CopyFrom(method.FullMethodName);
                            if (!burstModules.TryGetValue((int)method.MethodModuleFileIndex, out var isBurst))
                            {
                                isBurst = method.MethodModuleFile.FilePath.ToLowerInvariant().Contains("burst");
                                burstModules[(int)method.MethodModuleFileIndex] = isBurst;
                            }
                            copyCode = isBurst && funcData.Length > 0;
                        }
                        else
                        {
                            var jitData = func.MonoMethod;
                            funcData.BaseAddress = (ulong)jitData.CodeStart.ToInt64();
                            funcData.Length      = jitData.CodeSize;
                            funcData.Module      = discoveredModules.AddData(DiscoveredModule.FromMonoModule(jitData.Method.Module));

                            var fullName = MonoFunctionName(jitData.Method);
                            funcData.Name.CopyFrom(fullName);
                            copyCode = true;
                        }

                        if (copyCode)
                        {
                            funcData.CodeBlobOffset = blobData.Length;
                            blobData.AddRange((void *)funcData.BaseAddress, funcData.Length);
                        }
                        else
                        {
                            funcData.CodeBlobOffset = -1;
                        }

                        functions.Add(funcData);
                    }
                    profTrace.Functions = functions.ToArray(allocator);
                    profTrace.BlobData  = blobData.ToArray(allocator);
                }

                using (var modules = new NativeList <ModuleData>(Allocator.Temp))
                {
                    // make sure that all modules of the current process are included.
                    foreach (var module in trace.Processes[processIndex].LoadedModules)
                    {
                        discoveredModules.AddData(new DiscoveredModule {
                            Index = module.ModuleFile.ModuleFileIndex
                        });
                    }

                    ModuleData moduleData = default;
                    foreach (var dm in discoveredModules.Data)
                    {
                        if (dm.MonoModule != null)
                        {
                            moduleData          = default;
                            moduleData.IsMono   = true;
                            moduleData.FilePath = dm.MonoModule.Assembly.Location;
                        }
                        else
                        {
                            var module = trace.ModuleFiles[dm.Index];
                            moduleData.IsMono    = false;
                            moduleData.Checksum  = module.ImageChecksum;
                            moduleData.ImageBase = module.ImageBase;
                            moduleData.ImageEnd  = module.ImageEnd;
                            moduleData.PdbAge    = module.PdbAge;
                            moduleData.FilePath.CopyFrom(module.FilePath);
                            moduleData.PdbName.CopyFrom(module.PdbName);
                            var guidBytes = module.PdbSignature.ToByteArray();

                            fixed(byte *ptr = guidBytes)
                            UnsafeUtility.MemCpy(moduleData.PdbGuid, ptr, 16);
                        }
                        modules.Add(moduleData);
                    }
                    profTrace.Modules = modules.ToArray(allocator);
                }

                using (var threads = new NativeList <ThreadData>(Allocator.Temp))
                {
                    ThreadData threadData = default;
                    foreach (var t in discoveredThreads.Data)
                    {
                        var thread = trace.Threads[t];
                        threadData.ThreadName.CopyFrom(thread.ThreadInfo ?? "");
                        threads.Add(threadData);
                    }
                    profTrace.Threads = threads.ToArray(allocator);
                }
            }

            int GetFunctionIndex(TraceCodeAddress address)
            {
                var method = address.Method;

                if (method == null)
                {
                    var jit = Mono.GetJitInfoAnyDomain(new IntPtr((long)address.Address), out _);
                    if (jit.Method == null)
                    {
                        return(-1);
                    }
                    if (!monoFunctions.TryGetValue(jit.CodeStart, out var actualJit))
                    {
                        monoFunctions.Add(jit.CodeStart, jit);
                        actualJit = jit;
                    }
                    return(discoveredFunctions.AddData(DiscoveredFunction.FromMethod(actualJit)));
                }
                return(discoveredFunctions.AddData(new DiscoveredFunction
                {
                    Index = method.MethodIndex
                }));
            }

            string MonoFunctionName(MethodBase method)
            {
                if (method == null)
                {
                    return("???");
                }
                if (method.DeclaringType.DeclaringType == null)
                {
                    return(method.DeclaringType.Namespace + '.' + method.DeclaringType.Name + '.' + method.Name);
                }
                var outerMost = method.DeclaringType.DeclaringType;
                var outer     = method.DeclaringType;

                return(outerMost.Namespace + '.' + outerMost.Name + '+' + outer.Name + '.' + method.Name);
            }
        }
Beispiel #2
0
 public SignatureMatch(DiscoveredFunction fn, IReadOnlyList <VariantMatch> matches)
 {
     _function = fn;
     Matches   = matches;
 }
        public static IEnumerable <DiscoveredFunction> DiscoverFunctions <T>(this IEnumerable <T> insns, UInt32 startPc)
            where T : struct, IHasInstruction
        {
            var interp = new Interpreter <bool>(() => false);

            DiscoveredFunction current  = null;
            int  currentExtent          = 0;
            int  maxExtent              = 0;
            bool inFInalReturnDelaySlot = false;

            var preamble = interp.MetaHandlers.AllValid = (p, i) =>
            {
                current        = current ?? new DiscoveredFunction(p, 0);
                currentExtent += 4;

                if (inFInalReturnDelaySlot)
                {
                    ((IDiscoveredFunction)current).Size = (UInt32)currentExtent;

                    inFInalReturnDelaySlot = false;
                }

                return(true);
            };

            Func <UInt32, int, bool> noteMaxExtent = (pc, relOffset) =>
            {
                if (current == null)
                {
                    return(true);
                }

                if (relOffset < 0)
                {
                    return(false);
                }

                maxExtent = Math.Max(maxExtent, relOffset);

                return(true);
            };

            interp.Handlers.JR = (p, i) =>
            {
                preamble(p, i);

                var currentOffset = p - current.StartAddress;

                if (i.GprRs == 31 && maxExtent <= currentOffset)
                {
                    inFInalReturnDelaySlot = true;
                }

                return(true);
            };

            interp.Handlers.J = (p, i) =>
                                preamble(p, i) &&
                                noteMaxExtent(p, (int)(i.FullTarget(p) - current.StartAddress));

            interp.MetaHandlers.Branch = (p, i) =>
                                         preamble(p, i) &&
                                         noteMaxExtent(p, (int)(p + i.BranchOffset - current.StartAddress));

            foreach (var hasInsn in insns)
            {
                var insn = hasInsn.Instruction;

                // Skip leading nops if we aren't in any functions
                if (insn == 0 && current == null)
                {
                    goto cont;
                }

                var interpRval = interp.Execute(startPc, insn);

                if (current != null && insn.Id != MipsInstruction.Invalid)
                {
                    ((IDiscoveredFunction)current).AddInstruction(
                        new InstructionWithPc(startPc, insn)
                        );
                }

                if (current != null && (!interpRval || current.Size != 0))
                {
                    ((IDiscoveredFunction)current).Size = (uint)currentExtent;

                    if (current.Instructions.Any(i => i.Instruction.Id == MipsInstruction.JR && i.Instruction.GprRs == 31))
                    {
                        yield return(current);
                    }

                    current                = null;
                    currentExtent          = 0;
                    maxExtent              = 0;
                    inFInalReturnDelaySlot = false;
                }

cont:
                startPc += 4;
            }
        }