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