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 Antmicro.Renode.Peripherals.CPU;
9 using Antmicro.Renode.Peripherals.Bus;
10 using SysbusAccessWidth = Antmicro.Renode.Peripherals.Bus.SysbusAccessWidth;
11 using System.Collections.Generic;
12 using Antmicro.Renode.Logging;
13 
14 namespace Antmicro.Renode.Utilities.GDB.Commands
15 {
16     internal class BreakpointCommand : Command
17     {
BreakpointCommand(CommandsManager manager)18         public BreakpointCommand(CommandsManager manager) : base(manager)
19         {
20             watchpoints = new Dictionary<WatchpointDescriptor, int>();
21         }
22 
23         [Execute("Z")]
InsertBreakpoint( [Argument(Separator = B)]BreakpointType type, [Argument(Separator = B, Encoding = ArgumentAttribute.ArgumentEncoding.HexNumber)]ulong address, [Argument(Separator = B, Encoding = ArgumentAttribute.ArgumentEncoding.HexNumber)]uint kind)24         public PacketData InsertBreakpoint(
25             [Argument(Separator = ',')]BreakpointType type,
26             [Argument(Separator = ',', Encoding = ArgumentAttribute.ArgumentEncoding.HexNumber)]ulong address,
27             [Argument(Separator = ';', Encoding = ArgumentAttribute.ArgumentEncoding.HexNumber)]uint kind)
28         {
29             // The kind is the size of the breakpoint in bytes.
30             // Currently, we only handle it for watchpoints, where it specifies the number of bytes to watch.
31             // Setting a 0-byte watchpoint, for example with `watch *&variable_of_unknown_type`, gives
32             // unexpected results: GDB says the watchpoint is set, but it can never be hit.
33             if(kind == 0 && (type == BreakpointType.AccessWatchpoint
34                           || type == BreakpointType.ReadWatchpoint
35                           || type == BreakpointType.WriteWatchpoint))
36             {
37                 return PacketData.ErrorReply(Error.InvalidArgument);
38             }
39 
40             switch(type)
41             {
42                 case BreakpointType.MemoryBreakpoint:
43                     foreach(var cpu in manager.ManagedCpus)
44                     {
45                         cpu.AddHook(address, MemoryBreakpointHook);
46                     }
47                     break;
48                 case BreakpointType.HardwareBreakpoint:
49                     foreach(var cpu in manager.ManagedCpus)
50                     {
51                         cpu.AddHook(address, HardwareBreakpointHook);
52                     }
53                     break;
54                 case BreakpointType.AccessWatchpoint:
55                     AddWatchpointsCoveringMemoryArea(address, kind, Access.ReadAndWrite, AccessWatchpointHook);
56                     break;
57                 case BreakpointType.ReadWatchpoint:
58                     AddWatchpointsCoveringMemoryArea(address, kind, Access.Read, ReadWatchpointHook);
59                     break;
60                 case BreakpointType.WriteWatchpoint:
61                     AddWatchpointsCoveringMemoryArea(address, kind, Access.Write, WriteWatchpointHook);
62                     break;
63                 default:
64                     Logger.LogAs(this, LogLevel.Warning, "Unsupported breakpoint type: {0}, not inserting.", type);
65                     return PacketData.ErrorReply();
66             }
67 
68             return PacketData.Success;
69         }
70 
71         [Execute("z")]
RemoveBreakpoint( [Argument(Separator = B)]BreakpointType type, [Argument(Separator = B, Encoding = ArgumentAttribute.ArgumentEncoding.HexNumber)]ulong address, [Argument(Separator = B, Encoding = ArgumentAttribute.ArgumentEncoding.HexNumber)]uint kind)72         public PacketData RemoveBreakpoint(
73             [Argument(Separator = ',')]BreakpointType type,
74             [Argument(Separator = ',', Encoding = ArgumentAttribute.ArgumentEncoding.HexNumber)]ulong address,
75             [Argument(Separator = ';', Encoding = ArgumentAttribute.ArgumentEncoding.HexNumber)]uint kind)
76         {
77             switch(type)
78             {
79                 case BreakpointType.MemoryBreakpoint:
80                     foreach(var cpu in manager.ManagedCpus)
81                     {
82                         cpu.RemoveHook(address, MemoryBreakpointHook);
83                     }
84                     break;
85                 case BreakpointType.HardwareBreakpoint:
86                     foreach(var cpu in manager.ManagedCpus)
87                     {
88                         cpu.RemoveHook(address, HardwareBreakpointHook);
89                     }
90                     break;
91                 case BreakpointType.AccessWatchpoint:
92                     RemoveWatchpointsCoveringMemoryArea(address, kind, Access.ReadAndWrite, AccessWatchpointHook);
93                     break;
94                 case BreakpointType.ReadWatchpoint:
95                     RemoveWatchpointsCoveringMemoryArea(address, kind, Access.Read, ReadWatchpointHook);
96                     break;
97                 case BreakpointType.WriteWatchpoint:
98                     RemoveWatchpointsCoveringMemoryArea(address, kind, Access.Write, WriteWatchpointHook);
99                     break;
100                 default:
101                     Logger.LogAs(this, LogLevel.Warning, "Unsupported breakpoint type: {0}, not removing.", type);
102                     return PacketData.ErrorReply();
103             }
104 
105             return PacketData.Success;
106         }
107 
HardwareBreakpointHook(ICpuSupportingGdb cpu, ulong address)108         private void HardwareBreakpointHook(ICpuSupportingGdb cpu, ulong address)
109         {
110             cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, breakpointType: BreakpointType.HardwareBreakpoint));
111         }
112 
MemoryBreakpointHook(ICpuSupportingGdb cpu, ulong address)113         private void MemoryBreakpointHook(ICpuSupportingGdb cpu, ulong address)
114         {
115             cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, breakpointType: BreakpointType.MemoryBreakpoint));
116         }
117 
AccessWatchpointHook(ICpuSupportingGdb cpu, ulong address, SysbusAccessWidth width, ulong value)118         private void AccessWatchpointHook(ICpuSupportingGdb cpu, ulong address, SysbusAccessWidth width, ulong value)
119         {
120             //? I See a possible problem here.
121             //? Here we call `Halt` event with T05 argument, but in a second we will call it once again with S05 in HandleStepping@TranlationCPU.
122             //? It seems to work fine with GDB, but... I don't know if it is fully correct.
123             cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, address, BreakpointType.AccessWatchpoint));
124         }
125 
WriteWatchpointHook(ICpuSupportingGdb cpu, ulong address, SysbusAccessWidth width, ulong value)126         private void WriteWatchpointHook(ICpuSupportingGdb cpu, ulong address, SysbusAccessWidth width, ulong value)
127         {
128             cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, address, BreakpointType.WriteWatchpoint));
129         }
130 
ReadWatchpointHook(ICpuSupportingGdb cpu, ulong address, SysbusAccessWidth width, ulong value)131         private void ReadWatchpointHook(ICpuSupportingGdb cpu, ulong address, SysbusAccessWidth width, ulong value)
132         {
133             cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, address, BreakpointType.ReadWatchpoint));
134         }
135 
AddWatchpointsCoveringMemoryArea(ulong address, uint kind, Access access, BusHookDelegate hook)136         private void AddWatchpointsCoveringMemoryArea(ulong address, uint kind, Access access, BusHookDelegate hook)
137         {
138             // we need to register hooks for all possible access widths covering memory fragment
139             // [address, address + kind) referred by GDB
140             foreach(var descriptor in CalculateAllCoveringAddressess(address, kind, access, hook))
141             {
142                 lock(watchpoints)
143                 {
144                     if(watchpoints.ContainsKey(descriptor))
145                     {
146                         watchpoints[descriptor]++;
147                     }
148                     else
149                     {
150                         watchpoints.Add(descriptor, 1);
151                         manager.Machine.SystemBus.AddWatchpointHook(descriptor.Address, descriptor.Width, access, hook);
152                     }
153                 }
154             }
155         }
156 
RemoveWatchpointsCoveringMemoryArea(ulong address, uint kind, Access access, BusHookDelegate hook)157         private void RemoveWatchpointsCoveringMemoryArea(ulong address, uint kind, Access access, BusHookDelegate hook)
158         {
159             // we need to unregister hooks from all possible access widths convering memory fragment
160             // [address, address + kind) referred by GDB
161             foreach(var descriptor in CalculateAllCoveringAddressess(address, kind, access, hook))
162             {
163                 lock(watchpoints)
164                 {
165                     if(watchpoints[descriptor] > 1)
166                     {
167                         watchpoints[descriptor]--;
168                     }
169                     else
170                     {
171                         watchpoints.Remove(descriptor);
172                         manager.Machine.SystemBus.RemoveWatchpointHook(descriptor.Address, hook);
173                     }
174                 }
175             }
176         }
177 
CalculateAllCoveringAddressess(ulong address, uint kind, Access access, BusHookDelegate hook)178         private static IEnumerable<WatchpointDescriptor> CalculateAllCoveringAddressess(ulong address, uint kind, Access access, BusHookDelegate hook)
179         {
180             foreach(SysbusAccessWidth width in Enum.GetValues(typeof(SysbusAccessWidth)))
181             {
182                 for(var offset = -(long)(address % (ulong)width); offset < kind; offset += (long)width)
183                 {
184                     yield return new WatchpointDescriptor(address - (ulong)(-offset), width, access, hook);
185                 }
186             }
187         }
188 
189         private readonly Dictionary<WatchpointDescriptor, int> watchpoints;
190 
191         private class WatchpointDescriptor
192         {
WatchpointDescriptor(ulong address, SysbusAccessWidth width, Access access, BusHookDelegate hook)193             public WatchpointDescriptor(ulong address, SysbusAccessWidth width, Access access, BusHookDelegate hook)
194             {
195                 Address = address;
196                 Width = width;
197                 Access = access;
198                 Hook = hook;
199             }
200 
Equals(object obj)201             public override bool Equals(object obj)
202             {
203                 var objAsBreakpointDescriptor = obj as WatchpointDescriptor;
204                 if(objAsBreakpointDescriptor == null)
205                 {
206                     return false;
207                 }
208 
209                 return objAsBreakpointDescriptor.Address == Address
210                         && objAsBreakpointDescriptor.Width == Width
211                         && objAsBreakpointDescriptor.Access == Access
212                         && objAsBreakpointDescriptor.Hook == Hook;
213             }
214 
GetHashCode()215             public override int GetHashCode()
216             {
217                 return 17 * (int)Address
218                     + 23 * (int)Width
219                     + 17 * (int)Access
220                     + 17 * Hook.GetHashCode();
221             }
222 
223             public readonly ulong Address;
224             public readonly SysbusAccessWidth Width;
225             public readonly Access Access;
226             public readonly BusHookDelegate Hook;
227         }
228     }
229 }
230 
231