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