1 //
2 // Copyright (c) 2010-2025 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using System.Collections.Generic;
9 using Antmicro.Migrant;
10 using Antmicro.Renode.Exceptions;
11 using Antmicro.Renode.Logging;
12 
13 namespace Antmicro.Renode.Peripherals.CPU.GuestProfiling
14 {
15     [Transient]
16     public abstract class BaseProfiler : IDisposable
17     {
BaseProfiler(TranslationCPU cpu, bool flushInstantly, int? maximumNestedContexts)18         public BaseProfiler(TranslationCPU cpu, bool flushInstantly, int? maximumNestedContexts)
19         {
20             this.cpu = cpu;
21             this.flushInstantly = flushInstantly;
22 
23             if(maximumNestedContexts <= 0) {
24                 throw new ConstructionException("Optional maximumNestedContexts parameter must be a non-zero, positive integer.");
25             }
26             this.maximumNestedContexts = maximumNestedContexts;
27 
28             isFirstFrame = true;
29             bufferLock = new Object();
30             wholeExecution = new Dictionary<ulong, ProfilerContext>();
31             wholeExecution.Add(currentContextId, new ProfilerContext());
32         }
33 
Dispose()34         public virtual void Dispose()
35         {
36             FlushBuffer();
37             currentStack.Clear();
38             currentContext.Clear();
39             wholeExecution.Clear();
40         }
41 
StackFrameAdd(ulong currentAddress, ulong returnAddress, ulong instructionsCount)42         public abstract void StackFrameAdd(ulong currentAddress, ulong returnAddress, ulong instructionsCount);
StackFramePop(ulong currentAddress, ulong returnAddress, ulong instructionsCount)43         public abstract void StackFramePop(ulong currentAddress, ulong returnAddress, ulong instructionsCount);
OnContextChange(ulong newContextId)44         public abstract void OnContextChange(ulong newContextId);
InterruptEnter(ulong interruptIndex)45         public abstract void InterruptEnter(ulong interruptIndex);
InterruptExit(ulong interruptIndex)46         public abstract void InterruptExit(ulong interruptIndex);
FlushBuffer()47         public abstract void FlushBuffer();
48 
GetCurrentStack()49         public virtual string GetCurrentStack()
50         {
51             throw new RecoverableException($"Functionality is not supported by the currently selected profiler");
52         }
53 
GetSymbolName(ulong address)54         protected string GetSymbolName(ulong address)
55         {
56             if(!cpu.Bus.TryFindSymbolAt(address, out var name, out var _, cpu))
57             {
58                 // Symbol not found - return address must serve as a symbol
59                 name = $"0x{address:X}";
60             }
61             return name;
62         }
63 
PushCurrentContextSafe()64         protected void PushCurrentContextSafe()
65         {
66             if(maximumNestedContexts.HasValue && currentContext.Count >= maximumNestedContexts)
67             {
68                 cpu.Log(LogLevel.Warning, "Profiler: maximum nested contexts exceeded, disabling profiler");
69                 cpu.DisableProfiler();
70                 return;
71             }
72             currentContext.PushCurrentStack();
73         }
74 
75         protected readonly TranslationCPU cpu;
76         protected readonly bool flushInstantly;
77         protected readonly Object bufferLock;
78         // Keeps track of all of the created contexts. Key is the id of the thread
79         protected readonly Dictionary<ulong, ProfilerContext> wholeExecution;
80 
81         protected ProfilerContext currentContext => wholeExecution[currentContextId];
82         protected Stack<string> currentStack => currentContext.CurrentStack;
83         protected bool isFirstFrame;
84         protected ulong currentContextId;
85 
86         private readonly int? maximumNestedContexts;
87 
88         protected class ProfilerContext
89         {
ProfilerContext()90             public ProfilerContext()
91             {
92                 context = new Stack<Stack<string>>();
93                 currentStack = new Stack<string>();
94             }
95 
PopCurrentStack()96             public void PopCurrentStack()
97             {
98                 if(context.Count == 0)
99                 {
100                     currentStack = new Stack<string>();
101                 }
102                 else
103                 {
104                     currentStack = context.Pop();
105                 }
106             }
107 
PushCurrentStack()108             public void PushCurrentStack()
109             {
110                 context.Push(currentStack);
111                 currentStack = new Stack<string>();
112             }
113 
Clear()114             public void Clear()
115             {
116                 context.Clear();
117             }
118 
119             public int Count => context.Count;
120             public Stack<string> CurrentStack => currentStack;
121 
122             // We need to keep a stack of stacks as each context has its main execution and (potentially nested) interrupts
123             private readonly Stack<Stack<string>> context;
124             private Stack<string> currentStack;
125         }
126     }
127 }
128