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.Collections.Generic;
9 using System.Linq;
10 using System.Text;
11 using System.Reflection;
12 using Antmicro.Renode.Core;
13 using Antmicro.Renode.Logging;
14 using Antmicro.Renode.Exceptions;
15 using Antmicro.Renode.Peripherals.CPU;
16 using Antmicro.Renode.Peripherals.Bus;
17 using Antmicro.Renode.Utilities;
18 
19 namespace Antmicro.Renode.Debug
20 {
21     public static class SeL4Extensions
22     {
CreateSeL4(this ICpuSupportingGdb @this, ulong? debugThreadNameSyscallId = null)23         public static void CreateSeL4(this ICpuSupportingGdb @this, ulong? debugThreadNameSyscallId = null)
24         {
25             EmulationManager.Instance.CurrentEmulation.ExternalsManager.AddExternal(new SeL4DebugHelper(@this, debugThreadNameSyscallId), "seL4");
26         }
27     }
28 
29     public class SeL4DebugHelper : IExternal
30     {
SeL4DebugHelper(ICpuSupportingGdb cpu, ulong? debugThreadNameSyscallId)31         public SeL4DebugHelper(ICpuSupportingGdb cpu, ulong? debugThreadNameSyscallId)
32         {
33             if(cpu is Arm)
34             {
35                 this.callingConvention = new ArmCallingConvention(cpu);
36             }
37             else if(cpu is RiscV32)
38             {
39                 this.callingConvention = new RiscVCallingConvention(cpu);
40             }
41             else
42             {
43                 throw new RecoverableException("Only ARM and RV32 based platforms are supported by the seL4 extension");
44             }
45 
46             this.debugThreadNameSyscall = debugThreadNameSyscallId ?? DefaultDebugThreadNameSyscall;
47 
48             this.cpu = cpu;
49             this.mapping = new Dictionary<ulong, string>();
50             this.breakpoints = new Dictionary<ulong, HashSet<string>>();
51             this.temporaryBreakpoints = new Dictionary<ulong, HashSet<string>>();
52 
53             // Save restore_user_context as we will be using it pretty often
54             this.restoreUserContextAddress = cpu.Bus.GetSymbolAddress("restore_user_context");
55 
56             // handleUnknownSyscall function is handling seL4_DebugThreadName syscall.
57             // We are using this hook to inspect thread's TCB after it was initialized
58             var handleUnknownSyscallAddress = cpu.Bus.GetSymbolAddress("handleUnknownSyscall");
59             this.cpu.AddHook(handleUnknownSyscallAddress, HandleUnknownSyscall);
60             // When everything is set up and none of threads is working, this function will be called
61             // It seems to be always called after initialization of all CAmkES components
62             // so we can use it to check "readiness".
63             var idleThreadAddresss = cpu.Bus.GetSymbolAddress("idle_thread");
64             this.cpu.AddHook(idleThreadAddresss, Finalize);
65         }
66 
CurrentThread()67         public string CurrentThread()
68         {
69             if(callingConvention.PrivilegeMode == PrivilegeMode.Supervisor)
70             {
71                 return "kernel";
72             }
73             return CurrentThreadUnsafe();
74         }
75 
BreakOnNamingThread(string threadName)76         public void BreakOnNamingThread(string threadName)
77         {
78             pendingThreadName = threadName;
79         }
80 
BreakOnExittingUserspace(ExitUserspaceMode mode)81         public void BreakOnExittingUserspace(ExitUserspaceMode mode)
82         {
83             if(mode == exitUserspaceMode)
84             {
85                 return;
86             }
87 
88             if(exitUserspaceMode == ExitUserspaceMode.Never)
89             {
90                 cpu.AddHook(callingConvention.SyscallTrapAddress, HandleExitUserspace);
91             }
92             else if(mode == ExitUserspaceMode.Never)
93             {
94                 cpu.RemoveHook(callingConvention.SyscallTrapAddress, HandleExitUserspace);
95             }
96 
97             exitUserspaceMode = mode;
98         }
99 
100         // Sets the breakpoint on given address in chosen thread
101         // If address is not given, the breakpoint is set right after
102         // on the first instruction after context switch
SetBreakpoint(string threadName, ulong address = WildcardAddress)103         public void SetBreakpoint(string threadName, ulong address = WildcardAddress)
104         {
105             SetBreakpointHelper(threadName, address, breakpoints);
106         }
107 
108         // Similiar to SetBreakpoint, but for temporary breakpoints
SetTemporaryBreakpoint(string threadName, ulong address = WildcardAddress)109         public void SetTemporaryBreakpoint(string threadName, ulong address = WildcardAddress)
110         {
111             SetBreakpointHelper(threadName, address, temporaryBreakpoints);
112         }
113 
114         // Removes existing breakpoint on given address in chosen thread
115         // If address is not given, then breakpoint which happens on context switch
116         // is removed (see SetBreakpoint). If removeAll is set to true, all breakpoints for
117         // given thread are removed.
RemoveBreakpoint(string threadName, ulong address = WildcardAddress)118         public void RemoveBreakpoint(string threadName, ulong address = WildcardAddress)
119         {
120             RemoveBreakpointHelper(threadName, address, breakpoints);
121         }
122 
RemoveTemporaryBreakpoint(string threadName, ulong address = WildcardAddress)123         public void RemoveTemporaryBreakpoint(string threadName, ulong address = WildcardAddress)
124         {
125             RemoveBreakpointHelper(threadName, address, temporaryBreakpoints);
126         }
127 
RemoveAllBreakpoints(string threadName = null)128         public void RemoveAllBreakpoints(string threadName = null)
129         {
130             string realThreadName = null;
131             if(threadName != null && !TryGetRealThreadName(threadName, out realThreadName))
132             {
133                 return;
134             }
135 
136             foreach(var item in breakpoints.ToList())
137             {
138                 if(realThreadName != null)
139                 {
140                     item.Value.Remove(realThreadName);
141                 }
142                 if(realThreadName == null || item.Value.Count == 0)
143                 {
144                     breakpoints.Remove(item.Key);
145                 }
146                 if(GetBreakpointsCount(item.Key) == 0)
147                 {
148                     RemoveHook(item.Key);
149                 }
150             }
151             foreach(var item in temporaryBreakpoints.ToList())
152             {
153                 if(realThreadName != null)
154                 {
155                     item.Value.Remove(realThreadName);
156                 }
157                 if(realThreadName == null || item.Value.Count == 0)
158                 {
159                     temporaryBreakpoints.Remove(item.Key);
160                 }
161                 if(GetBreakpointsCount(item.Key) == 0)
162                 {
163                     RemoveHook(item.Key);
164                 }
165             }
166         }
167 
168         // Returns table with all the breakpoints. If threadName is set,
169         // returns only breakpoints set in given thread.
GetBreakpoints(string threadName = null)170         public string[,] GetBreakpoints(string threadName = null)
171         {
172             var entries = breakpoints.SelectMany(t => t.Value, (entry, thread) => new { Thread = thread, Address = entry.Key, Temporary = false })
173                 .Concat(temporaryBreakpoints.SelectMany(t => t.Value, (entry, thread) => new { Thread = thread, Address = entry.Key, Temporary = true }));
174 
175             if(threadName != null)
176             {
177                 entries = entries.Where(x => x.Thread.Contains(threadName));
178             }
179             var table = new Table().AddRow("Thread", "Address", "Temporary");
180             table.AddRows(entries,
181                     x => x.Thread == AnyThreadName ? "any" : x.Thread,
182                     x => x.Address == WildcardAddress ? "any" : "0x{0:X}".FormatWith(x.Address),
183                     x => x.Temporary.ToString());
184             if(exitUserspaceMode != ExitUserspaceMode.Never)
185             {
186                 table.AddRow("kernel", "any", (exitUserspaceMode == ExitUserspaceMode.Once).ToString());
187             }
188             return table.ToArray();
189         }
190 
191         // Returns list of all the breakpoints in script-friendly format: <THREAD_NAME>:<ADDRESS>\n.
192         // If threadName is set, returns only breakpoints set in given thread.
GetBreakpointsPlain(string threadName = null)193         public string GetBreakpointsPlain(string threadName = null)
194         {
195             var entries = breakpoints.SelectMany(t => t.Value, (entry, thread) => new { Thread = thread, Address = entry.Key })
196                 .Concat(temporaryBreakpoints.SelectMany(t => t.Value, (entry, thread) => new { Thread = thread, Address = entry.Key }));
197 
198             if(threadName != null)
199             {
200                 entries = entries.Where(x => x.Thread.Contains(threadName));
201             }
202             var output = entries.Select(entry => "{0}:{1}".FormatWith(
203                     entry.Thread,
204                     entry.Address == WildcardAddress ? "any" : "0x{0:X}".FormatWith(entry.Address)));
205             return string.Join("\n", output);
206         }
207 
208         public string[] Threads => mapping.Values.ToArray();
209 
210         public bool Ready { get; private set; }
211 
TryTranslateAddress(ICpuSupportingGdb cpu, ulong virtualAddress)212         private ulong TryTranslateAddress(ICpuSupportingGdb cpu, ulong virtualAddress)
213         {
214             if(cpu is ICPUWithMMU cpuWithMmu)
215             {
216                 virtualAddress = cpuWithMmu.TranslateAddress(virtualAddress, MpuAccess.Read);
217             }
218             return virtualAddress;
219         }
220 
HandleUnknownSyscall(ICpuSupportingGdb cpu, ulong address)221         private void HandleUnknownSyscall(ICpuSupportingGdb cpu, ulong address)
222         {
223             // Check if seL4_DebugThreadName was called
224             if((callingConvention.FirstArgument & 0xFFFFFFFF) != debugThreadNameSyscall)
225             {
226                 return;
227             }
228 
229             // We are in seL4_DebugThreadName handler, we don't need this hook anymore
230             cpu.RemoveHook(address, HandleUnknownSyscall);
231 
232             // This function will now call lookupIPCBuffer and lookupCapAndSlot
233             // We can temporarily hook those functions, and save theirs
234             // return addresses (which will be somewhere in handleUnknownSyscall)
235             // so we can use them later to "scrape" thread information.
236             // Additionally, we are getting address of ksCurThread variable
237             // which stores address of TCB of current thread.
238             var ksCurThreadAddress = cpu.Bus.GetSymbolAddress("ksCurThread");
239             var lookupIPCBufferAddress = cpu.Bus.GetSymbolAddress("lookupIPCBuffer");
240             var lookupCapAndSlotAddress = cpu.Bus.GetSymbolAddress("lookupCapAndSlot");
241 
242             // At this point we are sure, that we are in kernel context and ksCurrThread symbol vaddr
243             // will resolve properly. Therefore we can translate virtual address to physical address
244             // and use it to read memory. That allow us to check current TCB no matter in which
245             // context/privilege mode we are currently in, ignoring MMU completely.
246             ksCurThreadPhysAddress = TryTranslateAddress(cpu, ksCurThreadAddress);
247 
248             cpu.AddHook(lookupCapAndSlotAddress, HandleLookupCapAndSlotAddress);
249             cpu.AddHook(lookupIPCBufferAddress, HandleLookupIPCBuffer);
250         }
251 
Finalize(ICpuSupportingGdb cpu, ulong address)252         private void Finalize(ICpuSupportingGdb cpu, ulong address)
253         {
254             cpu.RemoveHook(address, Finalize);
255             Ready = true;
256             this.Log(LogLevel.Info, "Initialization complete.");
257         }
258 
HandleRestoreUserContext(ICpuSupportingGdb cpu, ulong address)259         private void HandleRestoreUserContext(ICpuSupportingGdb cpu, ulong address)
260         {
261             var threadName = CurrentThreadUnsafe();
262             if(!DoBreakpointExists(WildcardAddress, threadName))
263             {
264                 return;
265             }
266 
267             ulong tcbAddress = cpu.Bus.ReadDoubleWord(this.ksCurThreadPhysAddress, context: cpu);
268             if(!IsValidAddress(tcbAddress))
269             {
270                 this.Log(LogLevel.Debug, "Got invalid address for TCB, skipping");
271                 return;
272             }
273 
274             var nextPCAddress = TryTranslateAddress(cpu, tcbAddress + callingConvention.TCBNextPCOffset);
275 
276             if(!IsValidAddress(nextPCAddress))
277             {
278                 this.Log(LogLevel.Debug, "NextPC address in TCB is invalid, skipping");
279                 return;
280             }
281 
282             var pc = cpu.Bus.ReadDoubleWord(nextPCAddress, context: cpu);
283             cpu.AddHook(pc, HandleThreadSwitch);
284         }
285 
HandleThreadSwitch(ICpuSupportingGdb cpu, ulong address)286         private void HandleThreadSwitch(ICpuSupportingGdb cpu, ulong address)
287         {
288             var threadName = CurrentThread();
289             // Remove temporary breakpoint if exists
290             ClearTemporaryBreakpoint(WildcardAddress, threadName);
291             // We changed context, remove this hook as we don't need it anymore
292             cpu.RemoveHook(address, HandleThreadSwitch);
293             cpu.Pause();
294             cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, address, BreakpointType.MemoryBreakpoint));
295         }
296 
HandleBreakpoint(ICpuSupportingGdb cpu, ulong address)297         private void HandleBreakpoint(ICpuSupportingGdb cpu, ulong address)
298         {
299             var threadName = CurrentThread();
300             if(!DoBreakpointExists(address, threadName))
301             {
302                 return;
303             }
304 
305             ClearTemporaryBreakpoint(address, threadName);
306             cpu.Pause();
307             cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, address, BreakpointType.MemoryBreakpoint));
308         }
309 
HandleExitUserspace(ICpuSupportingGdb cpu, ulong address)310         private void HandleExitUserspace(ICpuSupportingGdb cpu, ulong address)
311         {
312             if(callingConvention.PrivilegeMode != PrivilegeMode.Supervisor)
313             {
314                 return;
315             }
316 
317             cpu.Pause();
318             cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, address, BreakpointType.MemoryBreakpoint));
319             if(exitUserspaceMode == ExitUserspaceMode.Once)
320             {
321                 cpu.RemoveHook(address, HandleExitUserspace);
322                 exitUserspaceMode = ExitUserspaceMode.Never;
323             }
324         }
325 
HandleLookupCapAndSlotAddress(ICpuSupportingGdb cpu, ulong address)326         private void HandleLookupCapAndSlotAddress(ICpuSupportingGdb cpu, ulong address)
327         {
328             // Save address to instruction in handleUnknownSyscall after call to lookupCapAndSlot
329             cpu.RemoveHook(address, HandleLookupCapAndSlotAddress);
330             cpu.AddHook(callingConvention.ReturnAddress, HandlePostLookupCapAndSlotAddress);
331         }
332 
HandlePostLookupCapAndSlotAddress(ICpuSupportingGdb cpu, ulong address)333         private void HandlePostLookupCapAndSlotAddress(ICpuSupportingGdb cpu, ulong address)
334         {
335             // Return value of lookupCapAndSlot is a structure
336             // with size of two machine words. We are interested in second value
337             // which is address of the capability (in this case TCB)
338             var luRet = callingConvention.ReturnValue;
339             var paddr = TryTranslateAddress(cpu, luRet + 0x4UL);
340             var underlying = cpu.Bus.ReadDoubleWord(paddr, context: cpu);
341             currentTCB = underlying & 0xffffffffffffff00;
342         }
343 
HandleLookupIPCBuffer(ICpuSupportingGdb cpu, ulong address)344         private void HandleLookupIPCBuffer(ICpuSupportingGdb cpu, ulong address)
345         {
346             // Save address to instruction in handleUnknownSyscall after call to lookupIPCBuffer
347             cpu.RemoveHook(address, HandleLookupIPCBuffer);
348             cpu.AddHook(callingConvention.ReturnAddress, HandlePostLookupIPCBuffer);
349         }
350 
HandlePostLookupIPCBuffer(ICpuSupportingGdb cpu, ulong address)351         private void HandlePostLookupIPCBuffer(ICpuSupportingGdb cpu, ulong address)
352         {
353             // In A0 register address to IPC buffer is returned.
354             // As seL4_DebugThreadName saves pointer to the string in IPC buffer,
355             // we can now just recover and read it.
356             var paddr = TryTranslateAddress(cpu, callingConvention.ReturnValue + 0x4UL);
357             var buffer = new List<byte>();
358 
359             // Maximum string size is MaximumMesageLength * size of machine word - 1
360             for(ulong i = 0; i < MaximumMessageLength * 4 - 1; ++i)
361             {
362                 var c = cpu.Bus.ReadByte(paddr + i, context: cpu);
363                 if(c == 0)
364                 {
365                     break;
366                 }
367                 buffer.Add(c);
368             }
369 
370             var threadName = System.Text.Encoding.ASCII.GetString(buffer.ToArray());
371 
372             // This function is called _after_ lookupCapAndSlot, therefore we now
373             // have both TCB address and thread's name. We can add it to our list
374             // of known threads.
375             if(!mapping.ContainsKey(currentTCB) || threadName.Contains("_control"))
376             {
377                 mapping[currentTCB] = threadName;
378             }
379 
380             // There was pendingThreadName set by WaitForThread function. As we have now all
381             // necessary information for requested thread, we can enter SingleStepMode
382             // (and thus return to prompt in GDB) so user can do something with it,
383             // e.g. create breakpoint on this thread.
384             if(pendingThreadName != null && threadName.Contains(pendingThreadName))
385             {
386                 pendingThreadName = null;
387                 cpu.Pause();
388                 cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, address, BreakpointType.MemoryBreakpoint));
389             }
390         }
391 
GetBreakpointsCount(ulong address)392         private int GetBreakpointsCount(ulong address)
393         {
394             breakpoints.TryGetValue(address, out var bp);
395             temporaryBreakpoints.TryGetValue(address, out var tbp);
396             return (bp?.Count ?? 0) + (tbp?.Count ?? 0);
397         }
398 
TryGetRealThreadName(string threadName, out string realThreadName)399         private bool TryGetRealThreadName(string threadName, out string realThreadName)
400         {
401             if(threadName == AnyThreadName)
402             {
403                 realThreadName = AnyThreadName;
404                 return true;
405             }
406 
407             realThreadName = mapping.Values.Where(thread => thread.Contains(threadName)).FirstOrDefault();
408             if(String.IsNullOrEmpty(realThreadName))
409             {
410                 this.Log(LogLevel.Warning, "No thread with name '{0}' found.", threadName);
411                 return false;
412             }
413 
414             return true;
415         }
416 
DoBreakpointExists(ulong address, string threadName)417         private bool DoBreakpointExists(ulong address, string threadName)
418         {
419             return (breakpoints.TryGetValue(address, out var bpList) && (bpList.Contains(AnyThreadName) || bpList.Contains(threadName))) ||
420                    (temporaryBreakpoints.TryGetValue(address, out var tbpList) && (tbpList.Contains(AnyThreadName) || tbpList.Contains(threadName)));
421         }
422 
AddContextSwitchHook()423         private void AddContextSwitchHook()
424         {
425             cpu.AddHook(restoreUserContextAddress, HandleRestoreUserContext);
426         }
427 
RemoveContextSwitchHook()428         private void RemoveContextSwitchHook()
429         {
430             cpu.RemoveHook(restoreUserContextAddress, HandleRestoreUserContext);
431         }
432 
ClearTemporaryBreakpoint(ulong address, string threadName)433         private void ClearTemporaryBreakpoint(ulong address, string threadName)
434         {
435             if(!temporaryBreakpoints.ContainsKey(address))
436             {
437                 return;
438             }
439 
440             temporaryBreakpoints[address].Remove(threadName);
441             temporaryBreakpoints[address].Remove(AnyThreadName);
442 
443             if(GetBreakpointsCount(address) == 0)
444             {
445                 RemoveHook(address);
446             }
447         }
448 
SetBreakpointHelper(string threadName, ulong address, Dictionary<ulong, HashSet<string>> breakpointsSource)449         private void SetBreakpointHelper(string threadName, ulong address, Dictionary<ulong, HashSet<string>> breakpointsSource)
450         {
451             if(!TryGetRealThreadName(threadName, out var realThreadName))
452             {
453                 return;
454             }
455 
456             if(!breakpointsSource.ContainsKey(address))
457             {
458                 breakpointsSource.Add(address, new HashSet<string>());
459             }
460 
461             if(!breakpointsSource[address].Add(realThreadName))
462             {
463                 this.Log(LogLevel.Warning, "This breakpoint already exists.");
464                 return;
465             }
466 
467             var breakpointsNum = GetBreakpointsCount(address);
468 
469             // Ignore if we already registered breakpoint for this address
470             if(breakpointsNum != 1)
471             {
472                 return;
473             }
474 
475             AddHook(address);
476         }
477 
RemoveBreakpointHelper(string threadName, ulong address, Dictionary<ulong, HashSet<string>> breakpointsSource)478         private void RemoveBreakpointHelper(string threadName, ulong address, Dictionary<ulong, HashSet<string>> breakpointsSource)
479         {
480             if(!breakpointsSource.TryGetValue(address, out var breakpoint))
481             {
482                 return;
483             }
484 
485             if(!TryGetRealThreadName(threadName, out var realThreadName))
486             {
487                 return;
488             }
489 
490             breakpoint.Remove(realThreadName);
491             var breakpointsNum = GetBreakpointsCount(address);
492             if(breakpointsNum != 0)
493             {
494                 return;
495             }
496 
497             RemoveHook(address);
498         }
499 
AddHook(ulong address)500         private void AddHook(ulong address)
501         {
502             if(address != WildcardAddress)
503             {
504                 cpu.AddHook(address, HandleBreakpoint);
505             }
506             else
507             {
508                 AddContextSwitchHook();
509             }
510         }
511 
RemoveHook(ulong address)512         private void RemoveHook(ulong address)
513         {
514             if(address != WildcardAddress)
515             {
516                 cpu.RemoveHook(address, HandleBreakpoint);
517             }
518             else
519             {
520                 RemoveContextSwitchHook();
521             }
522         }
523 
IsValidAddress(ulong address)524         private bool IsValidAddress(ulong address)
525         {
526             return !(address == 0x00000000 || address == 0xFFFFFFFF);
527         }
528 
CurrentThreadUnsafe()529         private string CurrentThreadUnsafe()
530         {
531             var tcb = cpu.Bus.ReadDoubleWord(this.ksCurThreadPhysAddress, context: cpu);
532             if(mapping.ContainsKey(tcb))
533             {
534                 return mapping[tcb];
535             }
536             return "unknown";
537         }
538 
539         public enum ExitUserspaceMode
540         {
541             Never,
542             Once,
543             Always,
544         }
545 
546         private const uint DefaultDebugThreadNameSyscall = 0xfffffff2;
547         private const string AnyThreadName = "<any>";
548         private const uint WildcardAddress = 0x00000000;
549         private const uint MaximumMessageLength = 120;
550 
551         private readonly Dictionary<ulong, HashSet<string>> breakpoints;
552         private readonly Dictionary<ulong, HashSet<string>> temporaryBreakpoints;
553         private readonly Dictionary<ulong, string> mapping;
554         private readonly ICpuSupportingGdb cpu;
555         private readonly ICallingConvention callingConvention;
556         private readonly ulong debugThreadNameSyscall;
557 
558         private ExitUserspaceMode exitUserspaceMode;
559         private bool breakpointsEnabled;
560         private ulong ksCurThreadPhysAddress;
561         private ulong restoreUserContextAddress;
562         private string pendingThreadName;
563         private ulong currentTCB;
564 
565         private interface ICallingConvention
566         {
567             ulong FirstArgument { get; }
568             ulong ReturnValue { get; }
569             ulong ReturnAddress { get; }
570             ulong SyscallTrapAddress { get; }
571             ulong TCBNextPCOffset { get; }
572             PrivilegeMode PrivilegeMode { get; }
573         }
574 
575         private enum PrivilegeMode
576         {
577             Userspace,
578             Supervisor,
579             Other,
580         }
581 
582         private class RiscVCallingConvention : ICallingConvention
583         {
RiscVCallingConvention(ICpuSupportingGdb cpu)584             public RiscVCallingConvention(ICpuSupportingGdb cpu)
585             {
586                 this.cpu = cpu;
587                 // Assumes that symbols for kernel are loaded
588                 syscallTrapAddress = cpu.Bus.GetSymbolAddress("trap_entry");
589             }
590 
591             public ulong FirstArgument => cpu.A[0];
592             public ulong ReturnValue => cpu.A[0];
593             public ulong ReturnAddress => cpu.RA;
594             public ulong SyscallTrapAddress => syscallTrapAddress;
595             public PrivilegeMode PrivilegeMode
596             {
597                 get
598                 {
599                     switch((byte)cpu.PRIV)
600                     {
601                         case 0b00:
602                             return PrivilegeMode.Userspace;
603                         case 0b01:
604                             return PrivilegeMode.Supervisor;
605                         default:
606                             return PrivilegeMode.Other;
607                     }
608                 }
609             }
610             public ulong TCBNextPCOffset => 34 * 4;
611 
612             private readonly ulong syscallTrapAddress;
613             private readonly dynamic cpu;
614         }
615 
616         private class ArmCallingConvention : ICallingConvention
617         {
ArmCallingConvention(ICpuSupportingGdb cpu)618             public ArmCallingConvention(ICpuSupportingGdb cpu)
619             {
620                 this.cpu = (Arm)cpu;
621                 // Assumes that symbols for kernel are loaded
622                 syscallTrapAddress = cpu.Bus.GetSymbolAddress("arm_swi_syscall");
623             }
624 
625             public ulong FirstArgument => cpu.R[0];
626             public ulong ReturnValue => cpu.R[0];
627             public ulong ReturnAddress => cpu.R[14];
628             public ulong SyscallTrapAddress => syscallTrapAddress;
629             public PrivilegeMode PrivilegeMode
630             {
631                 get
632                 {
633                     switch(cpu.CPSR & 0xfUL)
634                     {
635                         case 0b00:
636                             return PrivilegeMode.Userspace;
637                         case 0b11:
638                             return PrivilegeMode.Supervisor;
639                         default:
640                             return PrivilegeMode.Other;
641                     }
642                 }
643             }
644             public ulong TCBNextPCOffset => 15 * 4;
645 
646             private readonly ulong syscallTrapAddress;
647             private readonly Arm cpu;
648         }
649     }
650 }
651 
652