1 //
2 // Copyright (c) 2010-2024 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using Antmicro.Renode.Peripherals.CPU;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Logging;
12 using System.Collections.Generic;
13 using System.Linq;
14 using Antmicro.Renode.Exceptions;
15 using Antmicro.Renode.Peripherals.Bus;
16 using System.Text;
17 using Antmicro.Renode.Utilities;
18 
19 namespace Antmicro.Renode.Debug
20 {
21     public static class CPUTracerExtensions
22     {
CreateCPUTracer(this Arm cpu, string name)23         public static void CreateCPUTracer(this Arm cpu, string name)
24         {
25             EmulationManager.Instance.CurrentEmulation.AddOrUpdateInBag(name, new CPUTracer(cpu));
26         }
27     }
28 
29     public class CPUTracer : IExternal
30     {
CPUTracer(Arm cpu)31         public CPUTracer(Arm cpu)
32         {
33             this.cpu = cpu;
34             this.bus = cpu.Bus;
35         }
36 
TraceFunction(string name, IEnumerable<FunctionCallParameter> parameters, Action<TranslationCPU, ulong, string, IEnumerable<object>> callback, FunctionCallParameter? returnParameter = null, Action<TranslationCPU, ulong, string, IEnumerable<object>> returnCallback = null)37         public void TraceFunction(string name, IEnumerable<FunctionCallParameter> parameters, Action<TranslationCPU, ulong, string, IEnumerable<object>> callback,
38             FunctionCallParameter? returnParameter = null, Action<TranslationCPU, ulong, string, IEnumerable<object>> returnCallback = null)
39         {
40             if(registeredCallbacks.ContainsKey(name))
41             {
42                 throw new RecoverableException("Function {0} is already being traced.".FormatWith(name));
43             }
44             cpu.Log(LogLevel.Info, "Going to trace function '{0}'.", name);
45 
46             Symbol symbol;
47             try
48             {
49                 var address = cpu.Bus.GetSymbolAddress(name);
50                 symbol = cpu.Bus.GetLookup(cpu).GetSymbolByAddress(address);
51             }
52             catch(RecoverableException)
53             {
54                 cpu.Log(LogLevel.Warning, "Symbol {0} not found, exiting.", name);
55                 throw;
56             }
57 
58             var traceInfo = new TraceInfo();
59             traceInfo.Begin = symbol.Start.RawValue;
60             traceInfo.BeginCallback = (cpu, pc) => EvaluateTraceCallback(pc, name, parameters, callback);
61 
62             cpu.AddHook(traceInfo.Begin, traceInfo.BeginCallback);
63             if(returnCallback != null && returnParameter.HasValue)
64             {
65                 traceInfo.HasEnd = true;
66                 traceInfo.End = symbol.End.RawValue - (symbol.IsThumbSymbol ? 2 : 4UL);
67                 traceInfo.EndCallback = (cpu, pc) => EvaluateTraceCallback(pc, name, new[] { returnParameter.Value }, returnCallback);
68                 cpu.Log(LogLevel.Debug, "Address is @ 0x{0:X}, end is @ 0x{1:X}.", traceInfo.Begin, traceInfo.End);
69                 cpu.AddHook(traceInfo.End, traceInfo.EndCallback);
70             }
71             else
72             {
73                 cpu.Log(LogLevel.Debug, "Address is @ 0x{0:X}, end is not traced.", traceInfo.Begin);
74             }
75 
76             registeredCallbacks[name] = traceInfo;
77         }
78 
RemoveTracing(string name)79         public void RemoveTracing(string name)
80         {
81             TraceInfo traceInfo;
82             if(registeredCallbacks.TryGetValue(name, out traceInfo))
83             {
84                 cpu.Log(LogLevel.Info, "Removing trace from function '{0}'.", name);
85                 cpu.RemoveHook(traceInfo.Begin, traceInfo.BeginCallback);
86                 if(traceInfo.HasEnd)
87                 {
88                     cpu.RemoveHook(traceInfo.End, traceInfo.EndCallback);
89                 }
90             }
91             else
92             {
93                 cpu.Log(LogLevel.Warning, "Hook on function {0} not found, not removing.", name);
94             }
95         }
96 
EvaluateTraceCallback(ulong pc, string name, IEnumerable<FunctionCallParameter> parameters, Action<TranslationCPU, ulong, string, List<object>> callback)97         private void EvaluateTraceCallback(ulong pc, string name, IEnumerable<FunctionCallParameter> parameters, Action<TranslationCPU, ulong, string, List<object>> callback)
98         {
99             var regs = new List<object>();
100             var paramList = parameters.ToList();
101             //works only for 0-4 parameters!
102             for(int i = 0; i < Math.Min(paramList.Count, 4); i++)
103             {
104                 regs.Add(TranslateParameter(cpu.R[i], paramList[i]));
105             }
106             if(paramList.Count > 4)
107             {
108                 var offset = 0u;
109                 var sp = cpu.SP;
110                 for(int i = 4; i < paramList.Count; ++i)
111                 {
112                     regs.Add(TranslateParameter(cpu.Bus.ReadDoubleWord(sp + offset), paramList[i]));
113                     offset += 4; //does not support longer data types!
114                 }
115             }
116             callback(cpu, pc, name, regs);
117         }
118 
TranslateParameter(ulong value, FunctionCallParameter parameter)119         private object TranslateParameter(ulong value, FunctionCallParameter parameter)
120         {
121             var parameterType = parameter.Type;
122             var size = parameter.NumberOfElements;
123             switch(parameterType)
124             {
125             case FunctionCallParameterType.Ignore:
126                 return null;
127             case FunctionCallParameterType.Int64:
128                 return unchecked((long)value);
129             case FunctionCallParameterType.UInt64:
130                 return value;
131             case FunctionCallParameterType.Byte:
132                 return (byte)value;
133             case FunctionCallParameterType.Int32:
134                 return unchecked((int)value);
135             case FunctionCallParameterType.UInt32:
136                 return (uint)value;
137             case FunctionCallParameterType.Int16:
138                 return unchecked((short)value);
139             case FunctionCallParameterType.UInt16:
140                 return (ushort)value;
141             case FunctionCallParameterType.String:
142                 var done = false;
143                 var resultString = new StringBuilder();
144                 while(!done)
145                 {
146                     var readBytes = bus.ReadBytes(value, SizeOfStringBatch, context: cpu);
147                     for(var i = 0; i < readBytes.Length; ++i)
148                     {
149                         var currentByte = readBytes[i];
150                         if(currentByte == 0)
151                         {
152                             done = true;
153                             break;
154                         }
155                         if(currentByte >= 32 && currentByte < 127)
156                         {
157                             resultString.Append(Convert.ToChar(currentByte));
158                         }
159                     }
160                     value += SizeOfStringBatch;
161                 }
162                 return resultString.ToString();
163             case FunctionCallParameterType.ByteArray:
164                 return bus.ReadBytes(value, size, context: cpu);
165             case FunctionCallParameterType.Int32Array:
166             {
167                 var intResult = new int[size];
168                 var byteLength = size * sizeof(int);
169                 Buffer.BlockCopy(bus.ReadBytes(value, byteLength, context: cpu), 0, intResult, 0, byteLength);
170                 return intResult;
171             }
172             case FunctionCallParameterType.UInt32Array:
173             {
174                 var uintResult = new uint[size];
175                 var byteLength = size * sizeof(uint);
176                 Buffer.BlockCopy(bus.ReadBytes(value, byteLength, context: cpu), 0, uintResult, 0, byteLength);
177                 return uintResult;
178             }
179             default:
180                 throw new ArgumentOutOfRangeException();
181             }
182         }
183 
184         private readonly Dictionary<string, TraceInfo> registeredCallbacks = new Dictionary<string, TraceInfo>();
185         private readonly Arm cpu;
186         private readonly IBusController bus;
187 
188         private const int SizeOfStringBatch = 100;
189 
190         private struct TraceInfo
191         {
192             public ulong Begin;
193             public bool HasEnd;
194             public ulong End;
195             public Action<ICpuSupportingGdb, ulong> BeginCallback;
196             public Action<ICpuSupportingGdb, ulong> EndCallback;
197         }
198     }
199 }
200