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