1 //
2 // Copyright (c) 2010-2024 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.Concurrent;
9 using System.Collections.Generic;
10 using System.Linq;
11 using System.Threading;
12 using Antmicro.Migrant;
13 using Antmicro.Migrant.Hooks;
14 using Antmicro.Renode.Core;
15 using Antmicro.Renode.Exceptions;
16 using Antmicro.Renode.Logging;
17 using Antmicro.Renode.Logging.Profiling;
18 
19 namespace Antmicro.Renode.Peripherals.CPU
20 {
21     public static class ExecutionTracerExtensions
22     {
CreateExecutionTracing(this TranslationCPU @this, string name, string fileName, TraceFormat format, bool isBinary = false, bool compress = false, bool isSynchronous = false)23         public static void CreateExecutionTracing(this TranslationCPU @this, string name, string fileName, TraceFormat format, bool isBinary = false, bool compress = false, bool isSynchronous = false)
24         {
25             var writerBuilder = new TraceWriterBuilder(@this, fileName, format, isBinary, compress);
26             var tracer = new ExecutionTracer(@this, writerBuilder, isSynchronous);
27 
28             try
29             {
30                 // we keep it as external to dispose/flush on quit
31                 EmulationManager.Instance.CurrentEmulation.ExternalsManager.AddExternal(tracer, name);
32             }
33             catch(Exception)
34             {
35                 tracer.Dispose();
36                 throw new RecoverableException("ExecutionTracer is already running");
37             }
38 
39             tracer.Start();
40         }
41 
CreateExecutionTracingSynchronous(this TranslationCPU @this, string name, string fileName, TraceFormat format, bool isBinary = false, bool compress = false)42         public static void CreateExecutionTracingSynchronous(this TranslationCPU @this, string name, string fileName, TraceFormat format, bool isBinary = false, bool compress = false)
43         {
44             CreateExecutionTracing(@this, name, fileName, format, isBinary, compress, true);
45         }
46 
DisableExecutionTracing(this TranslationCPU @this)47         public static void DisableExecutionTracing(this TranslationCPU @this)
48         {
49             var em = EmulationManager.Instance.CurrentEmulation.ExternalsManager;
50             var tracers = em.GetExternalsOfType<ExecutionTracer>().Where(t => t.AttachedCPU == @this).ToList();
51             foreach(var tracer in tracers)
52             {
53                 tracer.Stop();
54                 em.RemoveExternal(tracer);
55             }
56         }
57     }
58 
59     public class ExecutionTracer : IDisposable, IExternal
60     {
ExecutionTracer(TranslationCPU cpu, TraceWriterBuilder traceWriterBuilder, bool isSynchronous = false)61         public ExecutionTracer(TranslationCPU cpu, TraceWriterBuilder traceWriterBuilder, bool isSynchronous = false)
62         {
63             AttachedCPU = cpu;
64             writerBuilder = traceWriterBuilder;
65             this.isSynchronous = isSynchronous;
66 
67             AttachedCPU.SetHookAtBlockEnd(HandleBlockEndHook);
68         }
69 
Dispose()70         public void Dispose()
71         {
72             Stop();
73         }
74 
Start()75         public void Start()
76         {
77             if(IsRunning)
78             {
79                 throw new RecoverableException("ExecutionTracer is already running");
80             }
81 
82             writer = writerBuilder.CreateWriter();
83             writer.WriteHeader();
84 
85             currentAdditionalData = new Queue<AdditionalData>();
86 
87             if(!isSynchronous)
88             {
89                 blocks = new BlockingCollection<Block>();
90 
91                 underlyingThread = new Thread(WriterThreadBody);
92                 underlyingThread.IsBackground = true;
93                 underlyingThread.Name = "Execution tracer worker";
94                 underlyingThread.Start();
95             }
96 
97             wasStarted = true;
98             wasStopped = false;
99         }
100 
Stop()101         public void Stop()
102         {
103             if(!IsRunning)
104             {
105                 return;
106             }
107             wasStopped = true;
108 
109             if(!isSynchronous)
110             {
111                 this.Log(LogLevel.Info, "Stopping the execution tracer worker and dumping the trace to a file...");
112                 blocks.CompleteAdding();
113                 underlyingThread.Join();
114                 underlyingThread = null;
115             }
116             else
117             {
118                 writer.FlushBuffer();
119             }
120 
121             this.Log(LogLevel.Info, "Execution tracer stopped");
122         }
123 
124         public TranslationCPU AttachedCPU { get; }
125 
126         [PreSerialization]
PreSerializationHook()127         private void PreSerializationHook()
128         {
129             Stop();
130         }
131 
132         [PostDeserialization]
PostDeserializationHook()133         private void PostDeserializationHook()
134         {
135             if(wasStarted)
136             {
137                 Start();
138             }
139         }
140 
WriterThreadBody()141         private void WriterThreadBody()
142         {
143             while(true)
144             {
145                 try
146                 {
147                     var block = blocks.Take();
148                     do
149                     {
150                         writer.Write(block);
151                     }
152                     while(blocks.TryTake(out block));
153                     writer.FlushBuffer();
154                 }
155                 catch(InvalidOperationException)
156                 {
157                     // this happens when the blocking collection is empty and is marked as completed - i.e., we are sure there will be no more elements
158                     break;
159                 }
160             }
161             writer.Dispose();
162         }
163 
TrackMemoryAccesses()164         public void TrackMemoryAccesses()
165         {
166             if(AttachedCPU.Architecture == "i386")
167             {
168                 throw new RecoverableException("This feature is not yet available on the X86 platforms.");
169             }
170             AttachedCPU.SetHookAtMemoryAccess((pc, operation, virtualAddress, physicalAddress, value) =>
171             {
172                 if(operation != MemoryOperation.InsnFetch)
173                 {
174                     currentAdditionalData.Enqueue(new MemoryAccessAdditionalData(pc, operation, virtualAddress, physicalAddress, value));
175                 }
176             });
177         }
178 
TrackVectorConfiguration()179         public void TrackVectorConfiguration()
180         {
181             if(!(AttachedCPU is ICPUWithPostOpcodeExecutionHooks) || !(AttachedCPU.Architecture.StartsWith("riscv")))
182             {
183                 throw new RecoverableException("This feature is not available on this platform");
184             }
185             var cpuWithPostOpcodeExecutionHooks = AttachedCPU as ICPUWithPostOpcodeExecutionHooks;
186             cpuWithPostOpcodeExecutionHooks.EnablePostOpcodeExecutionHooks(1u);
187             // 0x7057 it the fixed part of the vcfg opcodes
188             cpuWithPostOpcodeExecutionHooks.AddPostOpcodeExecutionHook(0x7057, 0x7057, (pc) =>
189             {
190                 var vl = AttachedCPU.GetRegister(RiscVVlRegisterIndex);
191                 var vtype = AttachedCPU.GetRegister(RiscVVtypeRegisterIndex);
192                 currentAdditionalData.Enqueue(new RiscVVectorConfigurationData(pc, vl.RawValue, vtype.RawValue));
193             });
194         }
195 
HandleBlockEndHook(ulong pc, uint instructionsInBlock)196         private void HandleBlockEndHook(ulong pc, uint instructionsInBlock)
197         {
198             if(instructionsInBlock == 0 || wasStopped)
199             {
200                 // ignore
201                 return;
202             }
203 
204             // We don't care if translation fails here (the address is unchanged in this case)
205             AttachedCPU.TryTranslateAddress(pc, MpuAccess.InstructionFetch, out var pcPhysical);
206 
207             var block = new Block
208             {
209                 FirstInstructionPC = pcPhysical,
210                 FirstInstructionVirtualPC = pc,
211                 InstructionsCount = instructionsInBlock,
212                 DisassemblyFlags = AttachedCPU.CurrentBlockDisassemblyFlags,
213                 AdditionalDataInTheBlock = currentAdditionalData,
214             };
215 
216             if(!isSynchronous)
217             {
218                 try
219                 {
220                     blocks.Add(block);
221                 }
222                 catch(Exception e)
223                 {
224                     this.Log(LogLevel.Warning, "The translation block that started at 0x{0:X} will not be traced and saved to file. The ExecutionTracer isn't ready yet.\n Underlying error : {1}", pc, e);
225                 }
226             }
227             else
228             {
229                 writer.Write(block);
230             }
231 
232             currentAdditionalData = new Queue<AdditionalData>();
233         }
234 
235         [Transient]
236         private TraceWriter writer;
237         [Transient]
238         private Thread underlyingThread;
239 
240         private bool IsRunning => wasStarted && !wasStopped;
241 
242         private BlockingCollection<Block> blocks;
243         private Queue<AdditionalData> currentAdditionalData;
244         private bool wasStarted;
245         private bool wasStopped;
246         private readonly bool isSynchronous;
247 
248         private readonly TraceWriterBuilder writerBuilder;
249 
250         private const int RiscVVlRegisterIndex = 3104;
251         private const int RiscVVtypeRegisterIndex = 3105;
252 
253         public struct Block
254         {
255             public ulong FirstInstructionPC;
256             public ulong FirstInstructionVirtualPC;
257             public ulong InstructionsCount;
258             public uint DisassemblyFlags;
259             public Queue<AdditionalData> AdditionalDataInTheBlock;
260 
ToStringAntmicro.Renode.Peripherals.CPU.ExecutionTracer.Block261             public override string ToString()
262             {
263                 return $"[Block: starting at 0x{FirstInstructionPC:X} with {InstructionsCount} instructions and {AdditionalDataInTheBlock.Count} additional data, flags: 0x{DisassemblyFlags:X}]";
264             }
265         }
266     }
267 }
268