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.Generic; 9 using System.Linq; 10 using System.Reflection; 11 using System.Collections; 12 using Antmicro.Renode.Core; 13 using Antmicro.Renode.Exceptions; 14 using Antmicro.Renode.Logging; 15 using Antmicro.Renode.Peripherals; 16 using Antmicro.Renode.Peripherals.CPU; 17 using Antmicro.Migrant; 18 using Antmicro.Migrant.Hooks; 19 20 namespace Antmicro.Renode.Utilities.GDB 21 { 22 public class CommandsManager 23 { CommandsManager(IMachine machine, IEnumerable<ICpuSupportingGdb> cpus)24 public CommandsManager(IMachine machine, IEnumerable<ICpuSupportingGdb> cpus) 25 { 26 availableCommands = new HashSet<CommandDescriptor>(); 27 typesWithCommands = new HashSet<string>(); 28 activeCommands = new HashSet<Command>(); 29 mnemonicList = new List<string>(); 30 Machine = machine; 31 CanAttachCPU = true; 32 33 commandsCache = new Dictionary<string, Command>(); 34 ManagedCpus = new ManagedCpusDictionary(); 35 foreach(var cpu in cpus) 36 { 37 if(!TryAddManagedCPU(cpu)) 38 { 39 throw new RecoverableException($"Could not create GDB server for CPU: {cpu.GetName()}"); 40 } 41 } 42 selectedCpu = ManagedCpus[PacketThreadId.Any]; 43 } 44 AttachCPU(ICpuSupportingGdb cpu)45 public void AttachCPU(ICpuSupportingGdb cpu) 46 { 47 if(!CanAttachCPU) 48 { 49 throw new RecoverableException("Cannot attach CPU because GDB is already connected."); 50 } 51 if(!TryAddManagedCPU(cpu)) 52 { 53 throw new RecoverableException("CPU already attached to this GDB server."); 54 } 55 InvalidateCompiledFeatures(); 56 } 57 IsCPUAttached(ICpuSupportingGdb cpu)58 public bool IsCPUAttached(ICpuSupportingGdb cpu) 59 { 60 return ManagedCpus.Contains(cpu); 61 } 62 SelectCpuForDebugging(ICpuSupportingGdb cpu)63 public void SelectCpuForDebugging(ICpuSupportingGdb cpu) 64 { 65 if(!ManagedCpus.Contains(cpu)) 66 { 67 throw new RecoverableException($"Tried to set invalid CPU: {cpu.GetName()} for debugging, which isn't connected to the current {nameof(GdbStub)}"); 68 } 69 selectedCpu = cpu; 70 } 71 Register(Type t)72 public void Register(Type t) 73 { 74 if(t == typeof(Command) || !typeof(Command).IsAssignableFrom(t)) 75 { 76 return; 77 } 78 79 typesWithCommands.Add(t.AssemblyQualifiedName); 80 AddCommandsFromType(t); 81 } 82 TryGetCommand(Packet packet, out Command command)83 public bool TryGetCommand(Packet packet, out Command command) 84 { 85 var mnemonic = packet.Data.MatchMnemonicFromList(mnemonicList); 86 if(mnemonic == null) 87 { 88 command = null; 89 return false; 90 } 91 92 if(!commandsCache.TryGetValue(mnemonic, out command)) 93 { 94 var commandDescriptor = availableCommands.FirstOrDefault(x => mnemonic == x.Mnemonic); 95 command = commandDescriptor == null ? null: GetOrCreateCommand(commandDescriptor.Method.DeclaringType); 96 commandsCache[mnemonic] = command; 97 } 98 return command != null; 99 } 100 GetCompiledFeatures()101 public List<GDBFeatureDescriptor> GetCompiledFeatures() 102 { 103 // GDB gets one feature description file and uses it for all threads. 104 // This method gathers features from all cores and unifies them. 105 106 // if unifiedFeatures contains any feature, it means that features were compiled and cached 107 if(unifiedFeatures.Any()) 108 { 109 return unifiedFeatures; 110 } 111 112 if(ManagedCpus.Count() == 1) 113 { 114 unifiedFeatures.AddRange(Cpu.GDBFeatures); 115 return unifiedFeatures; 116 } 117 118 var features = new Dictionary<string, List<GDBFeatureDescriptor>>(); 119 foreach(var cpu in ManagedCpus) 120 { 121 foreach(var feature in cpu.GDBFeatures) 122 { 123 if(!features.ContainsKey(feature.Name)) 124 { 125 features.Add(feature.Name, new List<GDBFeatureDescriptor>()); 126 } 127 features[feature.Name].Add(feature); 128 } 129 } 130 131 foreach(var featureVariations in features.Values) 132 { 133 unifiedFeatures.Add(UnifyFeature(featureVariations)); 134 } 135 136 return unifiedFeatures; 137 } 138 GetCompiledRegisters(int registerNumber)139 public GDBRegisterDescriptor[] GetCompiledRegisters(int registerNumber) 140 { 141 // The GDB backend relies a lot on getting a filtered list of 142 // registers. Extracting them from all features every time is 143 // costly, so this function caches the already filtered lists for 144 // faster retrieval. 145 146 if(unifiedRegisters.TryGetValue(registerNumber, out var registers)) 147 { 148 return registers; 149 } 150 151 registers = GetCompiledFeatures().SelectMany(f => f.Registers) 152 .Where(r => r.Number == registerNumber).ToArray(); 153 unifiedRegisters.Add(registerNumber, registers); 154 155 return registers; 156 } 157 158 public IMachine Machine { get; } 159 public ManagedCpusDictionary ManagedCpus { get; } 160 public bool CanAttachCPU { get; set; } 161 public ICpuSupportingGdb Cpu => selectedCpu; 162 UnifyFeature(List<GDBFeatureDescriptor> featureVariations)163 private static GDBFeatureDescriptor UnifyFeature(List<GDBFeatureDescriptor> featureVariations) 164 { 165 if(featureVariations.Count == 1) 166 { 167 return featureVariations[0]; 168 } 169 // This function unifies variations of a feature by taking the widest registers of matching name then adds taken register's type. 170 var unifiedFeature = new GDBFeatureDescriptor(featureVariations.First().Name); 171 var registers = new Dictionary<string, Tuple<GDBRegisterDescriptor, List<GDBCustomType>>>(); 172 var types = new Dictionary<string, Tuple<GDBCustomType, uint>>(); 173 foreach(var feature in featureVariations) 174 { 175 foreach(var register in feature.Registers) 176 { 177 if(!registers.ContainsKey(register.Name)) 178 { 179 registers.Add(register.Name, Tuple.Create(register, feature.Types)); 180 } 181 else if(register.Size > registers[register.Name].Item1.Size) 182 { 183 registers[register.Name] = Tuple.Create(register, feature.Types); 184 } 185 } 186 } 187 foreach(var pair in registers.Values.Where(elem => !String.IsNullOrEmpty(elem.Item1.Type)).OrderByDescending(elem => elem.Item1.Size)) 188 { 189 unifiedFeature.Registers.Add(pair.Item1); 190 AddFeatureTypes(ref types, pair.Item1.Type, pair.Item1.Size, pair.Item2); 191 } 192 foreach(var type in types.Values) 193 { 194 unifiedFeature.Types.Add(type.Item1); 195 } 196 return unifiedFeature; 197 } 198 AddFeatureTypes(ref Dictionary<string, Tuple<GDBCustomType, uint>> types, string id, uint width, List<GDBCustomType> source)199 private static void AddFeatureTypes(ref Dictionary<string, Tuple<GDBCustomType, uint>> types, string id, uint width, List<GDBCustomType> source) 200 { 201 if(types.TryGetValue(id, out var hit)) 202 { 203 if(hit.Item2 != width) 204 { 205 Logger.Log(LogLevel.Warning, string.Format("Found type \"{0}\" defined for multiple register widths ({1} and {2}). Reported target description may be erroneous.", id, hit.Item2, width)); 206 } 207 return; 208 } 209 210 var index = source.FindIndex(element => element.Attributes["id"] == id); 211 if(index == -1) 212 { 213 // If type description is not found, assume it's a built-in type. 214 return; 215 } 216 217 var type = source[index]; 218 foreach(var field in type.Fields) 219 { 220 if(field.TryGetValue("type", out var fieldType) && !types.ContainsKey(fieldType)) 221 { 222 AddFeatureTypes(ref types, fieldType, width, source); 223 } 224 } 225 types.Add(id, Tuple.Create(type, width)); 226 } 227 InvalidateCompiledFeatures()228 private void InvalidateCompiledFeatures() 229 { 230 if(unifiedFeatures.Any()) 231 { 232 unifiedFeatures.RemoveAll(_ => true); 233 } 234 } 235 TryAddManagedCPU(ICpuSupportingGdb cpu)236 private bool TryAddManagedCPU(ICpuSupportingGdb cpu) 237 { 238 if(IsCPUAttached(cpu)) 239 { 240 return false; 241 } 242 ManagedCpus.Add(cpu); 243 return true; 244 } 245 GetOrCreateCommand(Type t)246 private Command GetOrCreateCommand(Type t) 247 { 248 var result = activeCommands.SingleOrDefault(x => x.GetType() == t); 249 if(result == null) 250 { 251 result = (Command)Activator.CreateInstance(t, new[] { this }); 252 activeCommands.Add(result); 253 } 254 255 return result; 256 } 257 258 [PostDeserialization] ExtractCommandsFromTypeNames()259 private void ExtractCommandsFromTypeNames() 260 { 261 foreach(var typeName in typesWithCommands) 262 { 263 var t = Type.GetType(typeName); 264 AddCommandsFromType(t); 265 } 266 } 267 AddCommandsFromType(Type t)268 private void AddCommandsFromType(Type t) 269 { 270 var interestingMethods = Command.GetExecutingMethods(t); 271 if(interestingMethods.Length == 0) 272 { 273 Logger.Log(LogLevel.Error, string.Format("No proper constructor or executing methods found in type {0}", t.Name)); 274 return; 275 } 276 277 foreach(var interestingMethod in interestingMethods) 278 { 279 availableCommands.Add(new CommandDescriptor(interestingMethod)); 280 mnemonicList.Add(interestingMethod.GetCustomAttribute<ExecuteAttribute>().Mnemonic); 281 } 282 } 283 284 [Constructor] 285 private readonly HashSet<CommandDescriptor> availableCommands; 286 private readonly HashSet<string> typesWithCommands; 287 private readonly HashSet<Command> activeCommands; 288 private readonly List<GDBFeatureDescriptor> unifiedFeatures = new List<GDBFeatureDescriptor>(); 289 private readonly Dictionary<int, GDBRegisterDescriptor[]> unifiedRegisters = new Dictionary<int, GDBRegisterDescriptor[]>(); 290 291 private readonly Dictionary<string,Command> commandsCache; 292 private ICpuSupportingGdb selectedCpu; 293 [Constructor] 294 private readonly List<string> mnemonicList; 295 296 public class ManagedCpusDictionary : IEnumerable<ICpuSupportingGdb> 297 { 298 /// <remarks> There is no check whatsoever to prevent inserting the same CPU twice here, with different unique ID </remarks> Add(ICpuSupportingGdb cpu)299 public uint Add(ICpuSupportingGdb cpu) 300 { 301 // Thread id "0" might be interpreted as "any" thread by GDB, so start from 1 302 uint ctr = (uint)cpusToIds.Count + 1; 303 cpusToIds.Add(cpu, ctr); 304 idsToCpus.Add(ctr, cpu); 305 return ctr; 306 } 307 GetEnumerator()308 public IEnumerator<ICpuSupportingGdb> GetEnumerator() 309 { 310 return cpusToIds.Keys.GetEnumerator(); 311 } 312 IEnumerable.GetEnumerator()313 IEnumerator IEnumerable.GetEnumerator() 314 { 315 return this.GetEnumerator(); 316 } 317 318 public ICpuSupportingGdb this[int idx] 319 { 320 get 321 { 322 // There are two special cases here: 323 // -1 means "all" - not supported right now 324 // 0 means an arbitrary process or thread - so take the first one available 325 switch(idx) 326 { 327 case PacketThreadId.All: 328 throw new NotSupportedException("Selecting \"all\" CPUs is not supported"); 329 case PacketThreadId.Any: 330 return idsToCpus.OrderBy(kv => kv.Key).First().Value; 331 default: 332 return idsToCpus[(uint)idx]; 333 } 334 } 335 } 336 337 public ICpuSupportingGdb this[uint idx] => this[(int)idx]; 338 public uint this[ICpuSupportingGdb cpu] => cpusToIds[cpu]; 339 340 public IEnumerable<uint> GdbCpuIds => idsToCpus.Keys; 341 342 private readonly Dictionary<uint, ICpuSupportingGdb> idsToCpus = new Dictionary<uint, ICpuSupportingGdb>(); 343 private readonly Dictionary<ICpuSupportingGdb, uint> cpusToIds = new Dictionary<ICpuSupportingGdb, uint>(); 344 } 345 346 private class CommandDescriptor 347 { CommandDescriptor(MethodInfo method)348 public CommandDescriptor(MethodInfo method) 349 { 350 Method = method; 351 Mnemonic = method.GetCustomAttribute<ExecuteAttribute>().Mnemonic; 352 } 353 354 public string Mnemonic { get; private set; } 355 public MethodInfo Method { get; private set; } 356 } 357 } 358 } 359 360