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.Globalization;
10 using System.Linq;
11 using System.Reflection;
12 using System.Text;
13 using Antmicro.Renode.Logging;
14 using Antmicro.Renode.Peripherals.CPU;
15 using ELFSharp.ELF;
16 
17 namespace Antmicro.Renode.Utilities.GDB
18 {
19     public abstract class Command : IAutoLoadType
20     {
Execute(Command command, Packet packet)21         public static IEnumerable<PacketData> Execute(Command command, Packet packet)
22         {
23             var executeMethod = GetExecutingMethod(command, packet);
24             var mnemonic = packet.Data.Mnemonic;
25             var parsingContext = new ParsingContext(packet, mnemonic.Length);
26             var parameters = executeMethod.GetParameters().Select(x => HandleArgumentNotResolved(parsingContext, x)).ToArray();
27 
28             var result = executeMethod.Invoke(command, parameters);
29             if(result == null)
30             {
31                 return Enumerable.Empty<PacketData>();
32             }
33             else if(result is IEnumerable<PacketData> resultPackets)
34             {
35                 return resultPackets;
36             }
37             else if(result is PacketData resultPacket)
38             {
39                 return new [] { resultPacket };
40             }
41             throw new ArgumentOutOfRangeException("result",
42                 $"Result of GDB command method was of invalid type '{result.GetType().FullName}'");
43         }
44 
GetExecutingMethods(Type t)45         public static MethodInfo[] GetExecutingMethods(Type t)
46         {
47             if(t.GetConstructor(new[] { typeof(CommandsManager) }) == null)
48             {
49                 return new MethodInfo[0];
50             }
51 
52             return t.GetMethods().Where(x =>
53                 x.GetCustomAttribute<ExecuteAttribute>() != null &&
54                 x.GetParameters().All(y => y.GetCustomAttribute<ArgumentAttribute>() != null)).ToArray();
55         }
56 
Command(CommandsManager manager)57         protected Command(CommandsManager manager)
58         {
59             this.manager = manager;
60             executingMethods = new Dictionary<string, MethodInfo>();
61         }
62 
TryTranslateAddress(ulong address, out ulong translatedAddress, bool write)63         protected bool TryTranslateAddress(ulong address, out ulong translatedAddress, bool write)
64         {
65             var cpu = manager.Cpu as ICPUWithMMU;
66             if(cpu == null)
67             {
68                 translatedAddress = address;
69                 return true;
70             }
71 
72             var errorValue = ulong.MaxValue;
73             translatedAddress = errorValue;
74 
75             if(write)
76             {
77                 translatedAddress = cpu.TranslateAddress(address, MpuAccess.Write);
78 
79                 if(translatedAddress == errorValue)
80                 {
81                     Logger.LogAs(this, LogLevel.Warning, "Translation address failed for write access type!");
82                 }
83             }
84             else
85             {
86                 var fetchAddress = cpu.TranslateAddress(address, MpuAccess.InstructionFetch);
87                 var readAddress = cpu.TranslateAddress(address, MpuAccess.Read);
88 
89                 if(fetchAddress == errorValue && readAddress == errorValue)
90                 {
91                     Logger.LogAs(this, LogLevel.Warning, "Translation address failed for both read and instruction fetch access types!");
92                 }
93                 else if(fetchAddress == errorValue || readAddress == errorValue)
94                 {
95                     var fetchFailed = fetchAddress == errorValue;
96                     var failed = fetchFailed ? "instruction fetch" : "read";
97                     var fallback = fetchFailed ? "read" : "instruction fetch";
98                     Logger.LogAs(this, LogLevel.Debug, "Translation address failed for {0} access type! Returned translation for {1} access type.", failed, fallback);
99 
100                     translatedAddress = fetchFailed ? readAddress : fetchAddress;
101                 }
102                 else if(fetchAddress != readAddress)
103                 {
104                     Logger.LogAs(this, LogLevel.Warning, "Translation address missmatch for read and instruction fetch access types!");
105                 }
106                 else
107                 {
108                     translatedAddress = fetchAddress;
109                 }
110             }
111 
112             return translatedAddress != errorValue;
113         }
114 
GetTranslatedAccesses(ulong address, ulong length, bool write)115         protected IEnumerable<MemoryFragment> GetTranslatedAccesses(ulong address, ulong length, bool write)
116         {
117             var cpu = manager.Cpu as ICPUWithMMU;
118             if(cpu == null)
119             {
120                 // NOTE: If given CPU doesn't support MMU, we just assume 1:1 translation
121                 return new List<MemoryFragment>()
122                 {
123                     new MemoryFragment(address, length)
124                 };
125             }
126 
127             var pageSize = (ulong)cpu.PageSize;
128             var accesses = new List<MemoryFragment>();
129             var firstLength = Math.Min(length, pageSize - address % pageSize);
130 
131             if(!TryAddTranslatedMemoryFragment(ref accesses, address, firstLength, write))
132             {
133                 return null;
134             }
135             address += firstLength;
136             length -= firstLength;
137 
138             while(length > 0)
139             {
140                 var translateLength = Math.Min(pageSize, length);
141                 if(!TryAddTranslatedMemoryFragment(ref accesses, address, translateLength, write))
142                 {
143                     return null;
144                 }
145                 length -= translateLength;
146                 address += translateLength;
147             }
148 
149             return accesses;
150         }
151 
TryAddTranslatedMemoryFragment(ref List<MemoryFragment> accesses, ulong address, ulong length, bool write)152         protected bool TryAddTranslatedMemoryFragment(ref List<MemoryFragment> accesses, ulong address, ulong length, bool write)
153         {
154             if(length > 0)
155             {
156                 if(!TryTranslateAddress(address, out var translatedAddress, write))
157                 {
158                     Logger.LogAs(this, LogLevel.Warning, "Could not translate address 0x{0:X} to a valid physical address.", address);
159                     return false;
160                 }
161 
162                 accesses.Add(new MemoryFragment(translatedAddress, length));
163             }
164             return true;
165         }
166 
ExpandRegisterValue(ref StringBuilder data, int start, int end, int register)167         protected void ExpandRegisterValue(ref StringBuilder data, int start, int end, int register)
168         {
169             // register may have been reported with bigger Width, fill with zeros up to reported size
170             var isLittleEndian = manager.Cpu.Endianness == Endianess.LittleEndian;
171             var width = (end - start) * 4;
172             var reportedRegisters = manager.GetCompiledRegisters(register);
173             if(reportedRegisters.Any() && reportedRegisters.First().Size > width)
174             {
175                 data.Insert(isLittleEndian ? end : start, "00", ((int)reportedRegisters.First().Size - width) / 8);
176             }
177         }
178 
179         protected readonly CommandsManager manager;
180 
GetExecutingMethod(Command command, Packet packet)181         private static MethodInfo GetExecutingMethod(Command command, Packet packet)
182         {
183             var mnemonic = packet.Data.Mnemonic;
184             if(mnemonic == null)
185             {
186                 return null;
187             }
188             if(executingMethods.TryGetValue(mnemonic, out var output))
189             {
190                 return output;
191             }
192 
193             var interestingMethods = GetExecutingMethods(command.GetType());
194             if(!interestingMethods.Any())
195             {
196                 return null;
197             }
198 
199             // May return null if the given mnemonic is not supported. We should still put it in the executeMethods to avoid repeating the lookup
200             var method = interestingMethods.FirstOrDefault(x => x.GetCustomAttribute<ExecuteAttribute>().Mnemonic == mnemonic);
201 
202             executingMethods.Add(mnemonic, method);
203             return method;
204         }
205 
HandleArgumentNotResolved(ParsingContext context, ParameterInfo parameterInfo)206         private static object HandleArgumentNotResolved(ParsingContext context, ParameterInfo parameterInfo)
207         {
208             var attribute = parameterInfo.GetCustomAttribute<ArgumentAttribute>();
209             if(attribute == null)
210             {
211                 throw new ArgumentException(string.Format("Could not resolve argument: {0}", parameterInfo.Name));
212             }
213 
214             var startPosition = context.CurrentPosition;
215 
216             // we do not support multiple sets of operation+coreId parameters
217             if(startPosition > context.Packet.Data.DataAsString.Length)
218             {
219                 return parameterInfo.DefaultValue;
220             }
221 
222             var separatorPosition = attribute.Separator == '\0' ? -1 : context.Packet.Data.DataAsString.IndexOf(attribute.Separator, startPosition);
223             var length = (separatorPosition == -1 ? context.Packet.Data.DataAsString.Length : separatorPosition) - startPosition;
224             var valueToParse = context.Packet.Data.DataAsString.Substring(startPosition, length);
225             context.CurrentPosition += length + 1;
226 
227             switch(attribute.Encoding)
228             {
229                 case ArgumentAttribute.ArgumentEncoding.HexNumber:
230                     return Parse(parameterInfo.ParameterType, valueToParse, NumberStyles.HexNumber);
231                 case ArgumentAttribute.ArgumentEncoding.DecimalNumber:
232                     return Parse(parameterInfo.ParameterType, valueToParse);
233                 case ArgumentAttribute.ArgumentEncoding.BinaryBytes:
234                     return context.Packet.Data.DataAsBinary.Skip(startPosition).ToArray();
235                 case ArgumentAttribute.ArgumentEncoding.HexBytesString:
236                     return valueToParse.Split(2).Select(x => byte.Parse(x, NumberStyles.HexNumber)).ToArray();
237                 case ArgumentAttribute.ArgumentEncoding.HexString:
238                     return Encoding.UTF8.GetString(valueToParse.Split(2).Select(x => byte.Parse(x, NumberStyles.HexNumber)).ToArray());
239                 case ArgumentAttribute.ArgumentEncoding.String:
240                     return valueToParse;
241                 case ArgumentAttribute.ArgumentEncoding.ThreadId:
242                     return new PacketThreadId(valueToParse);
243                 default:
244                     throw new ArgumentException(string.Format("Unsupported argument type: {0}", parameterInfo.ParameterType.Name));
245             }
246         }
247 
Parse(Type type, string input, NumberStyles style = NumberStyles.Integer)248         private static object Parse(Type type, string input, NumberStyles style = NumberStyles.Integer)
249         {
250             if(type.IsEnum)
251             {
252                 return Parse(type.GetEnumUnderlyingType(), input, style);
253             }
254             if(type == typeof(int))
255             {
256                 return int.Parse(input, style);
257             }
258             if(type == typeof(uint))
259             {
260                 return uint.Parse(input, style);
261             }
262             if(type == typeof(ulong))
263             {
264                 return ulong.Parse(input, style);
265             }
266 
267             throw new ArgumentException(string.Format("Unsupported type for parsing: {0}", type.Name));
268         }
269 
270         private static Dictionary<string, MethodInfo> executingMethods;
271 
272         private class ParsingContext
273         {
ParsingContext(Packet packet, int currentPosition)274             public ParsingContext(Packet packet, int currentPosition)
275             {
276                 Packet = packet;
277                 CurrentPosition = currentPosition;
278             }
279 
280             public int CurrentPosition { get; set; }
281             public Packet Packet { get; set; }
282         }
283     }
284 
285     public struct MemoryFragment
286     {
MemoryFragmentAntmicro.Renode.Utilities.GDB.MemoryFragment287         public MemoryFragment(ulong address, ulong length)
288         {
289             Address = address;
290             Length = length;
291         }
292 
293         public ulong Address { get; }
294         public ulong Length { get; }
295     }
296 }
297 
298