1 // 2 // Copyright (c) 2010-2025 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 Antmicro.Renode.Core; 10 using Antmicro.Renode.Logging; 11 using Antmicro.Renode.Peripherals.CPU; 12 using Antmicro.Migrant; 13 using System.Linq; 14 using System.IO; 15 16 namespace Antmicro.Renode.Utilities.GDB 17 { 18 [Transient] 19 public class GdbStub : IDisposable, IExternal 20 { GdbStub(IMachine machine, IEnumerable<ICpuSupportingGdb> cpus, int port, bool autostartEmulation)21 public GdbStub(IMachine machine, IEnumerable<ICpuSupportingGdb> cpus, int port, bool autostartEmulation) 22 { 23 this.cpus = cpus; 24 Port = port; 25 26 LogsEnabled = true; 27 28 pcktBuilder = new PacketBuilder(); 29 commandsManager = new CommandsManager(machine, cpus); 30 TypeManager.Instance.AutoLoadedType += commandsManager.Register; 31 32 terminal = new SocketServerProvider(false, serverName: "GDB"); 33 terminal.DataReceived += OnByteWritten; 34 terminal.ConnectionAccepted += delegate 35 { 36 commandsManager.CanAttachCPU = false; 37 foreach(var cpu in commandsManager.ManagedCpus) 38 { 39 cpu.Halted += OnHalted; 40 cpu.ExecutionMode = ExecutionMode.SingleStep; 41 cpu.DebuggerConnected = true; 42 } 43 if(autostartEmulation && !EmulationManager.Instance.CurrentEmulation.IsStarted) 44 { 45 EmulationManager.Instance.CurrentEmulation.StartAll(); 46 } 47 }; 48 terminal.ConnectionClosed += delegate 49 { 50 foreach(var cpu in commandsManager.ManagedCpus) 51 { 52 cpu.Halted -= OnHalted; 53 cpu.ExecutionMode = ExecutionMode.Continuous; 54 cpu.DebuggerConnected = false; 55 } 56 commandsManager.CanAttachCPU = true; 57 }; 58 terminal.Start(port); 59 commHandler = new CommunicationHandler(this, commandsManager); 60 61 LogsEnabled = false; 62 } 63 AttachCPU(ICpuSupportingGdb cpu)64 public void AttachCPU(ICpuSupportingGdb cpu) 65 { 66 commandsManager.AttachCPU(cpu); 67 } 68 IsCPUAttached(ICpuSupportingGdb cpu)69 public bool IsCPUAttached(ICpuSupportingGdb cpu) 70 { 71 return commandsManager.IsCPUAttached(cpu); 72 } 73 Dispose()74 public void Dispose() 75 { 76 foreach(var cpu in cpus) 77 { 78 cpu.Halted -= OnHalted; 79 } 80 terminal.Dispose(); 81 } 82 83 public event Action<Stream> ConnectionAccepted 84 { 85 add => terminal.ConnectionAccepted += value; 86 remove => terminal.ConnectionAccepted -= value; 87 } 88 89 public int Port { get; private set; } 90 91 public bool LogsEnabled { get; set; } 92 93 public bool GdbClientConnected => !commandsManager.CanAttachCPU; 94 OnHalted(HaltArguments args)95 private void OnHalted(HaltArguments args) 96 { 97 using(var ctx = commHandler.OpenContext()) 98 { 99 // If we got here, and the CPU doesn't support Gdb (ICpuSupportingGdb) something went seriously wrong - this is GdbStub after all 100 var cpuSupportingGdb = (ICpuSupportingGdb)args.Cpu; 101 102 // We only should send one stop response to Gdb in all-stop mode 103 bool sendStopResponse = cpuSupportingGdb == stopReplyingCpu || stopReplyingCpu == null; 104 105 switch(args.Reason) 106 { 107 case HaltReason.Breakpoint: 108 switch(args.BreakpointType) 109 { 110 case BreakpointType.AccessWatchpoint: 111 case BreakpointType.WriteWatchpoint: 112 case BreakpointType.ReadWatchpoint: 113 case BreakpointType.HardwareBreakpoint: 114 case BreakpointType.MemoryBreakpoint: 115 if(commandsManager.Machine.SystemBus.IsMultiCore) 116 { 117 commandsManager.SelectCpuForDebugging(cpuSupportingGdb); 118 ctx.Send(new Packet(PacketData.StopReply(args.BreakpointType.Value, commandsManager.ManagedCpus[cpuSupportingGdb], args.Address))); 119 } 120 else 121 { 122 ctx.Send(new Packet(PacketData.StopReply(args.BreakpointType.Value, args.Address))); 123 } 124 break; 125 } 126 return; 127 case HaltReason.Pause: 128 if(commandsManager.Machine.InternalPause) 129 { 130 // Don't set Trap signal when the pause is internal as execution will 131 // be resumed after the reset is completed. This will cause GDB to stop and the emulation to continue 132 // resulting in a desync (eg. breakpoints will not be triggered) 133 return; 134 } 135 if(commandsManager.Machine.SystemBus.IsMultiCore) 136 { 137 if(sendStopResponse) 138 { 139 commandsManager.SelectCpuForDebugging(cpuSupportingGdb); 140 ctx.Send(new Packet(PacketData.StopReply(InterruptSignal, commandsManager.ManagedCpus[cpuSupportingGdb]))); 141 } 142 } 143 else 144 { 145 ctx.Send(new Packet(PacketData.StopReply(InterruptSignal))); 146 } 147 return; 148 case HaltReason.Step: 149 if(commandsManager.Machine.SystemBus.IsMultiCore) 150 { 151 commandsManager.SelectCpuForDebugging(cpuSupportingGdb); 152 ctx.Send(new Packet(PacketData.StopReply(TrapSignal, commandsManager.ManagedCpus[cpuSupportingGdb]))); 153 } 154 else 155 { 156 ctx.Send(new Packet(PacketData.StopReply(TrapSignal))); 157 } 158 return; 159 case HaltReason.Abort: 160 ctx.Send(new Packet(PacketData.AbortReply(AbortSignal))); 161 return; 162 default: 163 throw new ArgumentException("Unexpected halt reason"); 164 } 165 } 166 } 167 168 private ICpuSupportingGdb stopReplyingCpu; 169 OnByteWritten(int b)170 private void OnByteWritten(int b) 171 { 172 if(b == -1) 173 { 174 return; 175 } 176 var result = pcktBuilder.AppendByte((byte)b); 177 if(result == null) 178 { 179 return; 180 } 181 182 if(result.Interrupt) 183 { 184 if(LogsEnabled) 185 { 186 commandsManager.Cpu.Log(LogLevel.Noisy, "GDB CTRL-C occured - pausing CPU"); 187 } 188 189 // This weird syntax ensures we have unpaused cores to report first, and only if there are none, we will fall-back to halted ones 190 stopReplyingCpu = commandsManager.ManagedCpus.OrderByDescending(cpu => !cpu.IsHalted).FirstOrDefault(); 191 foreach(var cpu in commandsManager.ManagedCpus) 192 { 193 // This call is synchronous, so it's safe to assume that `stopReplyingCpu` will still be valid 194 cpu.Pause(); 195 } 196 stopReplyingCpu = null; 197 return; 198 } 199 200 using(var ctx = commHandler.OpenContext()) 201 { 202 if(result.CorruptedPacket) 203 { 204 if(LogsEnabled) 205 { 206 commandsManager.Cpu.Log(LogLevel.Warning, "Corrupted GDB packet received: {0}", result.Packet.Data.GetDataAsStringLimited()); 207 } 208 // send NACK 209 ctx.Send((byte)'-'); 210 return; 211 } 212 213 if(LogsEnabled) 214 { 215 commandsManager.Cpu.Log(LogLevel.Debug, "GDB packet received: {0}", result.Packet.Data.GetDataAsStringLimited()); 216 } 217 // send ACK 218 ctx.Send((byte)'+'); 219 220 Command command; 221 if(!commandsManager.TryGetCommand(result.Packet, out command)) 222 { 223 if(LogsEnabled) 224 { 225 commandsManager.Cpu.Log(LogLevel.Warning, "Unsupported GDB command: {0}", result.Packet.Data.GetDataAsStringLimited()); 226 } 227 ctx.Send(new Packet(PacketData.Empty)); 228 } 229 else 230 { 231 IEnumerable<PacketData> packetDatas; 232 try 233 { 234 packetDatas = Command.Execute(command, result.Packet); 235 } 236 catch(Exception e) 237 { 238 if(LogsEnabled) 239 { 240 commandsManager.Cpu.Log(LogLevel.Debug, "{0}", e); 241 // Get to the inner-most exception. The outer-most exception here is often 242 // 'Reflection.TargetInvocationException' which doesn't have any useful message. 243 while(e.InnerException != null) 244 { 245 e = e.InnerException; 246 } 247 var commandString = result.Packet.Data.GetDataAsStringLimited(); 248 commandsManager.Cpu.Log(LogLevel.Error, "GDB '{0}' command failed: {1}", commandString, e.Message); 249 } 250 ctx.Send(new Packet(PacketData.ErrorReply(Error.Unknown))); 251 return; 252 } 253 // If there is no data here, we will respond later with Stop Reply Response 254 foreach(var packetData in packetDatas) 255 { 256 ctx.Send(new Packet(packetData)); 257 } 258 } 259 } 260 } 261 262 private readonly PacketBuilder pcktBuilder; 263 private readonly IEnumerable<ICpuSupportingGdb> cpus; 264 private readonly SocketServerProvider terminal; 265 private readonly CommandsManager commandsManager; 266 private readonly CommunicationHandler commHandler; 267 268 private const int InterruptSignal = 2; 269 private const int TrapSignal = 5; 270 private const int AbortSignal = 6; 271 272 private class CommunicationHandler 273 { CommunicationHandler(GdbStub stub, CommandsManager manager)274 public CommunicationHandler(GdbStub stub, CommandsManager manager) 275 { 276 this.stub = stub; 277 this.manager = manager; 278 queue = new Queue<byte>(); 279 internalLock = new object(); 280 } 281 OpenContext()282 public Context OpenContext() 283 { 284 lock(internalLock) 285 { 286 counter++; 287 if(counter > 1) 288 { 289 if(stub.LogsEnabled) 290 { 291 manager.Cpu.Log(LogLevel.Debug, "Gdb stub: entering nested communication context. All bytes will be queued."); 292 } 293 } 294 return new Context(this, counter > 1); 295 } 296 } 297 SendByteDirect(byte b)298 public void SendByteDirect(byte b) 299 { 300 stub.terminal.SendByte(b); 301 } 302 SendAllBufferedData()303 private void SendAllBufferedData() 304 { 305 foreach(var b in queue) 306 { 307 stub.terminal.SendByte(b); 308 } 309 queue.Clear(); 310 } 311 ContextClosed(IEnumerable<byte> buffer)312 private void ContextClosed(IEnumerable<byte> buffer) 313 { 314 lock(internalLock) 315 { 316 if(buffer != null) 317 { 318 foreach(var b in buffer) 319 { 320 queue.Enqueue(b); 321 } 322 } 323 324 counter--; 325 if(counter == 0 && queue.Count > 0) 326 { 327 if(stub.LogsEnabled) 328 { 329 manager.Cpu.Log(LogLevel.Debug, "Gdb stub: leaving nested communication context. Sending {0} queued bytes.", queue.Count); 330 } 331 SendAllBufferedData(); 332 } 333 } 334 } 335 336 private readonly GdbStub stub; 337 private readonly CommandsManager manager; 338 private readonly Queue<byte> queue; 339 private readonly object internalLock; 340 private int counter; 341 342 public class Context : IDisposable 343 { Context(CommunicationHandler commHandler, bool useBuffering)344 public Context(CommunicationHandler commHandler, bool useBuffering) 345 { 346 this.commHandler = commHandler; 347 if(useBuffering) 348 { 349 buffer = new Queue<byte>(); 350 } 351 } 352 Dispose()353 public void Dispose() 354 { 355 commHandler.ContextClosed(buffer); 356 } 357 Send(Packet packet)358 public void Send(Packet packet) 359 { 360 if(commHandler.stub.LogsEnabled) 361 { 362 commHandler.manager.Cpu.Log(LogLevel.Debug, "Sending response to GDB: {0}", packet.Data.GetDataAsStringLimited()); 363 } 364 foreach(var b in packet.GetCompletePacket()) 365 { 366 Send(b); 367 } 368 } 369 Send(byte b)370 public void Send(byte b) 371 { 372 if(buffer == null) 373 { 374 commHandler.SendByteDirect(b); 375 } 376 else 377 { 378 buffer.Enqueue(b); 379 } 380 } 381 382 private readonly CommunicationHandler commHandler; 383 private readonly Queue<byte> buffer; 384 } 385 } 386 } 387 } 388 389