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