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.Threading;
9 using System.Collections.Generic;
10 using System.Linq;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Peripherals.CPU;
14 using Antmicro.Renode.Utilities.GDB;
15 using Antmicro.Renode.Time;
16 
17 namespace Antmicro.Renode.Extensions.Utilities.GDB.Commands
18 {
19     public class MultithreadContinueCommand : Command, IMultithreadCommand
20     {
MultithreadContinueCommand(CommandsManager manager)21         public MultithreadContinueCommand(CommandsManager manager) : base(manager)
22         {
23         }
24 
25         [Execute("vCont?")]
GetSupportedActions()26         public PacketData GetSupportedActions()
27         {
28             // packets `C` is deprecated for multi-threading support, but at the same time it is required in a valid reply
29             return new PacketData("vCont;c;C;s");
30         }
31 
32         [Execute("vCont;")]
Continue([Argument(Encoding = ArgumentAttribute.ArgumentEncoding.String)]string data)33         public PacketData Continue([Argument(Encoding = ArgumentAttribute.ArgumentEncoding.String)]string data)
34         {
35             if(!TryParseData(data, out var operations))
36             {
37                 return PacketData.ErrorReply();
38             }
39 
40             if(EmulationManager.Instance.CurrentEmulation.SingleStepBlocking)
41             {
42                 if(!TryHandleBlockingExecution(ref operations))
43                 {
44                     return PacketData.ErrorReply();
45                 }
46             }
47 
48             foreach(var operation in operations)
49             {
50                 ManageOperation(operation);
51             }
52 
53             return null;
54         }
55 
TryHandleBlockingExecution(ref IEnumerable<Operation> operations)56         private bool TryHandleBlockingExecution(ref IEnumerable<Operation> operations)
57         {
58             /* This method solves problem of blocking, i.e.:
59              *  Given two cores A and B and time intervals of their time source T(n) (current time interval), where:
60              *   - core A reports finishing T(n) and wants to obtain T(n+1), and
61              *   - core B has some work left to execute in T(n).
62              *  Time source won't grant T(n+1) until all of its sinks report finishing T(n).
63              *  Calling Step on core A before having core B finish T(n) would block.
64              *  To avoid such a scenario:
65              *   1) `operations` are split into:
66              *     - cores, similar to core B, that have some work left - `blocking`
67              *     - cores, similar to core A, that have to obtian new time interval - `blocked`
68              *   2) cores in `blocking` are resumed to do available work
69              *   3) if any of those cores has some work left to execute in current time interval then consume the time left for those cores
70              *  At this point all cores have finished current time interval and it's safe to perform Step for cores from `blocked`.
71              */
72             // step 1)
73             var blocking = operations.Where(operation => !manager.ManagedCpus[operation.CoreId].TimeHandle.IsDone).ToList();
74             var blocked = operations.Where(operation => manager.ManagedCpus[operation.CoreId].TimeHandle.IsDone).ToList();
75 
76             // if there is no operation that would block then return and execute `operations` in any order
77             var blockedStep = blocked.Where(operation => operation.Type == OperationType.Step).ToList();
78             if(!blockedStep.Any())
79             {
80                 // we support only all-stop mode, skip `continue` requests if we got any `step` request
81                 var stepping = blocking.Where(operation => operation.Type == OperationType.Step).ToList();
82                 if(stepping.Any())
83                 {
84                     operations = stepping;
85                 }
86                 return true;
87             }
88             // preempt cores from waiting on time request and switch to SingleStep mode
89             // this helps to keep control of the cores as none of them will successfully obtain a new time interval
90             foreach(var operation in operations)
91             {
92                 // Switching the core into SingleStep will cause a spurious StopReply to be sent to Gdb
93                 // TODO: this should be addressed
94                 manager.ManagedCpus[operation.CoreId].ExecutionMode = ExecutionMode.SingleStep;
95                 manager.ManagedCpus[operation.CoreId].TimeHandle.DelayGrant = true;
96             }
97 
98             // we support only all-stop mode, skip `continue` requests
99             operations = blockedStep;
100             // abort is set when core fails to stop at the sync-point in the callbacks
101             var abort = false;
102             var blockingContinue = blocking.Where(operation => operation.Type == OperationType.Continue).ToList();
103             var cde = new CountdownEvent(blockingContinue.Count());
104 
105             // a part of step 3)
106             // make sure that cores with Continue reach the sync-point
107             // pause execution at sync-point or skip time if core stopped before sync-point
108             foreach(var operation in blockingContinue)
109             {
110                 var callbacks = new Dictionary<uint, Action>();
111                 var cpu = manager.ManagedCpus[operation.CoreId];
112                 Action callback = () =>
113                 {
114                     var isDone = cpu.TimeHandle.IsDone;
115                     // if core didn't stop at a breakpoint (would change ExecutionMode) or didn't reach the sync-point then let it continue
116                     if(cpu.ExecutionMode == ExecutionMode.Continuous && !isDone)
117                     {
118                         return;
119                     }
120                     cpu.TimeHandle.ReportedBack -= callbacks[operation.CoreId];
121                     cpu.ExecutionMode = ExecutionMode.SingleStep;
122                     // skip time if core stopped before sync-point
123                     if(!isDone)
124                     {
125                         if(!cpu.TimeHandle.TrySkipToSyncPoint(out var interval))
126                         {
127                             cpu.Log(LogLevel.Error, "Aborted execution of gdb command, core #{0} would block.", operation.CoreId);
128                             abort = true;
129                             return;
130                         }
131                         cpu.Log(LogLevel.Warning, "Jumped {0}s in time.", interval);
132                     }
133                     cde.Signal();
134                 };
135                 cpu.TimeHandle.ReportedBack += callback;
136                 callbacks.Add(operation.CoreId, callback);
137             }
138             // step 2)
139             // run blocking cores
140             foreach(var operation in blocking)
141             {
142                 ManageOperation(operation);
143             }
144             // the rest of step 3)
145             // wait for the blocking cores to get to the sync-point
146             cde.Wait();
147             if(abort)
148             {
149                 return false;
150             }
151             foreach(var operation in blocking.Where(operation => operation.Type != OperationType.Continue))
152             {
153                 var cpu = manager.ManagedCpus[operation.CoreId];
154                 // skip time if core didn't get to the sync-point
155                 if(!cpu.TimeHandle.IsDone)
156                 {
157                     if(!cpu.TimeHandle.TrySkipToSyncPoint(out var interval))
158                     {
159                         cpu.Log(LogLevel.Error, "Aborted execution of gdb command, core #{0} would block.", operation.CoreId);
160                         abort = true;
161                     }
162                     cpu.Log(LogLevel.Warning, "Jumped {0}s in time.", interval);
163                 }
164             }
165             return true;
166         }
167 
TryParseData(string data, out IEnumerable<Operation> operations)168         private bool TryParseData(string data, out IEnumerable<Operation> operations)
169         {
170             operations = Enumerable.Empty<Operation>();
171             var operationsList = new List<Operation>();
172             // ATM we support only all-stop mode, so if we receive a `step` request in the packet we skip the `continue` request,
173             // but don't skip for synchronus execution, its needed in TryHandleBlockingExecution
174             var skipContinue = !EmulationManager.Instance.CurrentEmulation.SingleStepBlocking && data.Contains('s') && data.Contains('c');
175             var gdbCpuIdsToHandle = new HashSet<uint>(manager.ManagedCpus.GdbCpuIds);
176             foreach(var pair in data.Split(';'))
177             {
178                 // No id means that command should be applied to the rest of the threads
179                 var coreId = AllCores;
180                 var operation = pair.Split(':');
181                 if(pair.Length > 1)
182                 {
183                     coreId = int.Parse(operation[1]);
184                 }
185 
186                 var type = Operation.ParseType(operation[0]);
187                 if(!type.HasValue)
188                 {
189                     manager.Cpu.Log(LogLevel.Error, "Encountered an unsupported operation in command: \"{0}\".", pair);
190                     return false;
191                 }
192                 if(skipContinue && type == OperationType.Continue)
193                 {
194                     continue;
195                 }
196 
197                 if(coreId == AllCores)
198                 {
199                     foreach(var id in gdbCpuIdsToHandle)
200                     {
201                         operationsList.Add(new Operation(id, type.Value));
202                     }
203                     gdbCpuIdsToHandle.Clear();
204                 }
205                 else if(coreId == AnyCore)
206                 {
207                     if(gdbCpuIdsToHandle.Count == 0)
208                     {
209                         manager.Cpu.Log(LogLevel.Error, "No CPUs available to execute \"{0}\" command.", pair);
210                         return false;
211                     }
212                     var firstAvailable = gdbCpuIdsToHandle.First();
213                     operationsList.Add(new Operation(firstAvailable, type.Value));
214                     gdbCpuIdsToHandle.Remove(firstAvailable);
215                 }
216                 else
217                 {
218                     // coreId has proper core id
219                     if(!gdbCpuIdsToHandle.Remove((uint)coreId))
220                     {
221                         var index = operationsList.FindIndex(op => op.CoreId == (uint)coreId);
222                         if(index != -1)
223                         {
224                             manager.Cpu.Log(LogLevel.Error, "CPU #{1} already set to {2}, error in \"{0}\" command.", pair, coreId, operationsList[index].Type);
225                         }
226                         else
227                         {
228                             manager.Cpu.Log(LogLevel.Error, "Invalid CPU id in \"{0}\" command.", pair);
229                         }
230                         return false;
231                     }
232                     operationsList.Add(new Operation((uint)coreId, type.Value));
233                 }
234             }
235             if(!operationsList.Any())
236             {
237                 manager.Cpu.Log(LogLevel.Error, "No actions specified.");
238                 return false;
239             }
240             foreach(var id in gdbCpuIdsToHandle)
241             {
242                 operationsList.Add(new Operation(id, manager.ManagedCpus[id].ExecutionMode == ExecutionMode.Continuous ? OperationType.Continue : OperationType.None));
243             }
244             operations = operationsList;
245             return true;
246         }
247 
ManageOperation(Operation operation)248         private void ManageOperation(Operation operation)
249         {
250             var cpu = manager.ManagedCpus[operation.CoreId];
251             switch(operation.Type)
252             {
253                 case OperationType.Continue:
254                     cpu.ExecutionMode = ExecutionMode.Continuous;
255                     cpu.Resume();
256                     break;
257                 case OperationType.Step:
258                     cpu.Step(1);
259                     break;
260                 case OperationType.None:
261                     break;
262                 default:
263                     cpu.Log(LogLevel.Info, "Encountered an unsupported operation.");
264                     break;
265             }
266         }
267 
268         private const int AllCores = PacketThreadId.All;
269         private const int AnyCore = PacketThreadId.Any;
270 
271         private struct Operation
272         {
OperationAntmicro.Renode.Extensions.Utilities.GDB.Commands.MultithreadContinueCommand.Operation273             public Operation(uint id, OperationType type) : this()
274             {
275                 this.CoreId = id;
276                 this.Type = type;
277             }
278 
ParseTypeAntmicro.Renode.Extensions.Utilities.GDB.Commands.MultithreadContinueCommand.Operation279             public static OperationType? ParseType(string str)
280             {
281                 switch(str)
282                 {
283                     case "c":
284                         return OperationType.Continue;
285                     case "s":
286                         return OperationType.Step;
287                     default:
288                         return null;
289                 }
290             }
291 
292             public uint CoreId { get; }
293             public OperationType Type { get; }
294         }
295 
296         private enum OperationType
297         {
298             Continue,
299             Step,
300             None,
301         }
302     }
303 }
304