Merge "sim:config: add tools for debugging userspace"
diff --git a/gdbscript.py b/gdbscript.py
new file mode 100755
index 0000000..b4ed8ac
--- /dev/null
+++ b/gdbscript.py
@@ -0,0 +1,303 @@
+#!/usr/bin/env python
+
+import os
+import os.path
+from glob import glob
+
+
+def source(callable):
+    """Convenience decorator for sourcing gdb commands"""
+    callable()
+    return callable
+
+
+@source
+class seL4(gdb.Command):
+    """Utility functions for debugging seL4 applications"""
+    def __init__(self):
+        super(self.__class__, self).__init__('sel4', gdb.COMMAND_USER, prefix=True)
+
+
+@source
+class seL4BreakpointsEnabled(gdb.Command):
+    """Enable or disable breakpoints (disabled by default)"""
+
+    def __init__(self):
+        super(self.__class__, self).__init__('sel4 breakpoints-enabled', gdb.COMMAND_STATUS)
+
+    def complete(self, text, words):
+        return [c for c in ['True', 'False'] if c.startswith(text)]
+
+    def invoke(self, arg, from_tty):
+        if arg:
+            breakpoints_enabled(arg)
+        else:
+            print(breakpoints_enabled())
+
+
+@source
+class seL4Threads(gdb.Command):
+    """Lists all seL4 threads"""
+
+    def __init__(self):
+        super(self.__class__, self).__init__('sel4 threads', gdb.COMMAND_STATUS)
+
+    def invoke(self, arg, from_tty):
+        print('\n'.join(get_threads()))
+
+
+@source
+class seL4Thread(gdb.Command):
+    """Returns current seL4 thread"""
+
+    def __init__(self):
+        super(self.__class__, self).__init__('sel4 thread', gdb.COMMAND_STATUS)
+
+    def invoke(self, arg, from_tty):
+        print(get_current_thread())
+
+@source
+class seL4WaitForThread(gdb.Command):
+    """Waits for given thread to be avaiable"""
+
+    def __init__(self):
+        super(self.__class__, self).__init__('sel4 wait-for-thread', gdb.COMMAND_BREAKPOINTS)
+
+    def complete(self, text, word):
+        return list(thread for thread in get_threads() if thread.startswith(text))
+
+    def invoke(self, arg, from_tty):
+        gdb.execute('mon seL4 WaitForThread "{}"'.format(arg))
+        gdb.execute('continue')
+
+
+@source
+class seL4SwitchSymbols(gdb.Command):
+    """Forcibly switches symbols to ones of the given component
+
+    sel4 switch-symbols [component]
+
+    Forcibly sets symbol-file to file corresponding to given component.
+    If no comonent name is given or thread is "default" then default
+    binary is loaded instead."""
+
+    def __init__(self):
+        super(self.__class__, self).__init__('sel4 switch-symbols', gdb.COMMAND_FILES)
+
+    def complete(self, text, word):
+        global BINARIES
+
+        components = list(BINARIES.keys())
+        components.append('default')
+        return list(component for component in components if component.startswith(text))
+
+    def invoke(self, arg, from_tty):
+        if not arg or arg == 'default':
+            switch_symbols(verbose=True)
+        else:
+            switch_symbols(arg, fallback=False, verbose=True)
+
+
+@source
+class seL4Break(gdb.Command):
+    """Creates breakpoint
+
+    sel4 break thread [address]
+
+    Creates breakpoint in <thread> on optional <address>
+    If no address is given, the breakpoint will be set on
+    first address after context-switch"""
+
+    def __init__(self):
+        super(self.__class__, self).__init__('sel4 break', gdb.COMMAND_BREAKPOINTS)
+
+    def complete(self, text, word):
+        words = text.split(' ')
+        if not text or len(words) <= 1:
+            return list(thread for thread in get_threads() if thread.startswith(word))
+        elif len(words) == 2:
+            return gdb.COMPLETE_SYMBOL
+        else:
+            return gdb.COMPLETE_NONE
+
+    def invoke(self, arg, from_tty):
+        args = arg.split(' ')
+
+        if len(args) < 1 or not args[0]:
+            raise gdb.GdbError("sel4 break: thread argument is required")
+
+        thread = args[0] if len(args) >= 1 else None
+        symbol_name = args[1] if len(args) >= 2 else None
+
+        if symbol_name:
+            symbol, _ = gdb.lookup_symbol(symbol_name)
+            address = int(symbol.value().address)
+            gdb.execute('mon seL4 SetBreakpoint "{}" {}'.format(thread, hex(address)))
+        else:
+            gdb.execute('mon seL4 SetBreakpoint "{}"'.format(thread))
+
+
+@source
+class seL4Delete(gdb.Command):
+    """Removes breakpoint
+
+    sel4 delete thread [address]
+
+    Removes given breakpoint. If no address is given, the context-switch
+    breakpoint is removed"""
+
+    def __init__(self):
+        super(self.__class__, self).__init__('sel4 delete', gdb.COMMAND_BREAKPOINTS)
+
+    def complete(self, text, word):
+        words = text.split(' ')
+        if not text or len(words) <= 1:
+            return list(thread for thread in get_threads() if thread.startswith(word))
+        elif len(words) == 2:
+            breakpoints = get_breakpoints(words[0])
+            lines = breakpoints.split('\n')[3:][:-1]
+            completions = []
+            for line in lines:
+                values = line.split('|')
+                thread = values[1].strip()
+
+                if words[0] not in thread:
+                    continue
+
+                if 'null' in values[2]:
+                    continue
+
+                address = values[2].strip();
+                if address.startswith(word):
+                    completions.append(address)
+            return completions
+        else:
+            return gdb.COMPLETE_NONE
+
+    def invoke(self, arg, from_tty):
+        args = arg.split(' ')
+
+        if len(args) < 1 or not args[0]:
+            raise gdb.GdbError("sel4 delete: thread argument is required")
+
+        thread = args[0] if len(args) >= 1 else None
+        address = args[1] if len(args) >= 2 else None
+
+        if address:
+            gdb.execute('mon seL4 RemoveBreakpoint "{}" {}'.format(thread, address))
+        else:
+            gdb.execute('mon seL4 RemoveBreakpoint "{}"'.format(thread))
+
+
+@source
+class seL4DeleteAll(gdb.Command):
+    """Removes all breakpoints assigned to thread
+
+    sel4 delete-all thread"""
+
+    def __init__(self):
+        super(self.__class__, self).__init__('sel4 delete-all', gdb.COMMAND_BREAKPOINTS)
+
+    def complete(self, text, word):
+        return list(thread for thread in get_threads() if thread.startswith(text))
+
+    def invoke(self, arg, from_tty):
+        gdb.execute('mon seL4 RemoveBreakpoint "{}" 0x0 True'.format(arg))
+
+
+@source
+class seL4ListBreakpoints(gdb.Command):
+    """Lists all breakpoints
+
+    sel4 list-breakpoints [thread]
+    """
+
+    def __init__(self):
+        super(self.__class__, self).__init__('sel4 list-breakpoints', gdb.COMMAND_BREAKPOINTS)
+
+    def complete(self, text, word):
+        return list(thread for thread in get_threads() if thread.startswith(text))
+
+    def invoke(self, arg, from_tty):
+        print(get_breakpoints())
+
+
+@source
+class seL4Ready(gdb.Command):
+    """Returns ready state of seL4 extensions"""
+
+    def __init__(self):
+        super(self.__class__, self).__init__('sel4 ready', gdb.COMMAND_STATUS)
+
+    def invoke(self, arg, from_tty):
+        gdb.execute('mon seL4 Ready')
+
+
+@source
+def find_binaries():
+    global BINARIES, DEFAULT_BINARY
+    BINARIES = {}
+
+    out = os.environ['OUT']
+    kata_path = os.path.join(out, 'kata', 'riscv32-unknown-elf', 'debug')
+    DEFAULT_BINARY = os.path.join(kata_path, 'kernel', 'kernel.elf')
+
+    images_path = os.path.join(kata_path, '*.instance.bin')
+    for image_path in glob(images_path):
+        image_name = os.path.basename(image_path).split('.instance.bin')[0]
+        BINARIES[image_name] = image_path
+
+    BINARIES['rootserver'] = os.path.join(kata_path, 'capdl-loader')
+
+
+def get_breakpoints(thread=None):
+    if not thread:
+        breakpoints = gdb.execute('mon seL4 GetBreakpoints', to_string=True).strip()
+    else:
+        breakpoints = gdb.execute('mon seL4 GetBreakpoints \"{}\"'.format(thread), to_string=True).strip()
+    return breakpoints
+
+
+def get_current_thread():
+    return gdb.execute('mon seL4 CurrentThread', to_string=True).strip()
+
+
+def get_threads():
+    threads = ', '.join(gdb.execute('mon seL4 Threads', to_string=True).strip().split('\n')[1:-1])
+    return [thread.strip() for thread in threads.split(',') if thread.strip()]
+
+
+def breakpoints_enabled(value=None):
+    if value:
+        gdb.execute('mon seL4 BreakpointsEnabled {}'.format(value))
+    else:
+        return gdb.execute('mon seL4 BreakpointsEnabled', to_string=True).strip()
+
+
+def switch_symbols(thread=None, fallback=True, verbose=False):
+    global BINARIES, DEFAULT_BINARY
+
+    if thread is None:
+        gdb.execute('symbol-file {}'.format(DEFAULT_BINARY), from_tty=verbose)
+        return
+
+    image_name = next((key for key in BINARIES.keys() if key in thread), None)
+    if image_name is not None:
+        image_path = BINARIES[image_name]
+    elif image_name is None and fallback:
+        image_path = DEFAULT_BINARY
+    else:
+        raise gdb.GdbError("no symbol-file found for thread/component {}".format(thread))
+
+    gdb.execute('symbol-file {}'.format(image_path), from_tty=verbose)
+
+
+def stop_handler(event):
+    if not isinstance(event, gdb.StopEvent):
+        return
+
+    thread = get_current_thread()
+    switch_symbols(thread)
+
+
+gdb.events.stop.connect(stop_handler)
diff --git a/shodan.resc b/shodan.resc
index 94f75f6..7ec6df1 100644
--- a/shodan.resc
+++ b/shodan.resc
@@ -12,6 +12,7 @@
 include @sim/config/shodan_infrastructure/SmcRiscV32.cs
 include @sim/config/shodan_infrastructure/OpenTitan2_UART.cs
 include @sim/config/shodan_infrastructure/Mailbox.cs
+include @sim/config/shodan_infrastructure/seL4Extensions.cs
 
 machine LoadPlatformDescription @sim/config/platforms/shodan.repl
 
@@ -40,8 +41,10 @@
 
 # Load the system software tar and model ELF into external flash
 $tar ?= @out/ext_flash_release.tar
+$kernel ?= @out/kata/riscv32-unknown-elf/release/kernel/kernel.elf
 sysbus LoadBinary $tar 0x44000000
 sysbus LoadBinary @out/springbok_iree/samples/quant_model/person_detection_emitc_static 0x46000000
+sysbus LoadSymbolsFrom $kernel
 
 # Start both cores at the bootrom entry point.
 sysbus.cpu0 PC 0x8090
diff --git a/shodan_infrastructure/seL4Extensions.cs b/shodan_infrastructure/seL4Extensions.cs
new file mode 100644
index 0000000..b4b09c9
--- /dev/null
+++ b/shodan_infrastructure/seL4Extensions.cs
@@ -0,0 +1,278 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Antmicro.Renode.Core;
+using Antmicro.Renode.Logging;
+using Antmicro.Renode.Peripherals.CPU;
+using Antmicro.Renode.Peripherals.Bus;
+using Antmicro.Renode.Utilities;
+
+namespace Antmicro.Renode.Debug
+{
+    public static class seL4Extensions
+    {
+        public static void CreateseL4(this RiscV32 @this)
+        {
+            EmulationManager.Instance.CurrentEmulation.ExternalsManager.AddExternal(new seL4(@this), "seL4");
+        }
+    }
+
+    public class seL4 : IExternal
+    {
+        public seL4(RiscV32 cpu)
+        {
+            this.cpu = cpu;
+            this.mapping = new Dictionary<ulong, string>();
+            this.breakpoints = new Dictionary<string, SortedSet<ulong?>>();
+
+            var handleUnknownSyscallAddress = bus.GetSymbolAddress("handleUnknownSyscall");
+            this.cpu.AddHook(handleUnknownSyscallAddress, HandleUnknownSyscall);
+            var idleThreadAddresss = bus.GetSymbolAddress("idle_thread");
+            this.cpu.AddHook(idleThreadAddresss, Finalize);
+        }
+
+        public string CurrentThread()
+        {
+            var tcb = bus.ReadDoubleWord(this.ksCurThreadPhysAddress, context: cpu);
+            if(!mapping.ContainsKey(tcb))
+            {
+                return "default";
+            }
+            return mapping[tcb];
+        }
+
+        public void WaitForThread(string threadName)
+        {
+            pendingThreadName = threadName;
+        }
+
+        public void SetBreakpoint(string threadName, ulong? address = null)
+        {
+            var realThreadName = mapping.Values.Where(thread => thread.Contains(threadName)).FirstOrDefault();
+            if(String.IsNullOrEmpty(realThreadName))
+            {
+                this.Log(LogLevel.Warning, "No thread with name '{0}' found.", threadName);
+                return;
+            }
+
+            if(!breakpoints.ContainsKey(realThreadName))
+            {
+                breakpoints.Add(realThreadName, new SortedSet<ulong?>());
+            }
+
+            if(!breakpoints[realThreadName].Add(address))
+            {
+                this.Log(LogLevel.Warning, "This breakpoint already exists.");
+            }
+        }
+
+        public void RemoveBreakpoint(string threadName, ulong? address = null, bool removeAll = false)
+        {
+            var realThreadName = mapping.Values.Where(thread => thread.Contains(threadName)).FirstOrDefault();
+            if(String.IsNullOrEmpty(realThreadName))
+            {
+                this.Log(LogLevel.Warning, "No thread with name '{0}' found.", threadName);
+                return;
+            }
+
+            if(!breakpoints.ContainsKey(realThreadName))
+            {
+                return;
+            }
+
+            if(removeAll)
+            {
+                foreach(var addr in breakpoints[realThreadName])
+                {
+                    if(addr != null)
+                    {
+                        cpu.RemoveHook(addr.Value, HandleBreakpoint);
+                    }
+                }
+
+                breakpoints.Remove(realThreadName);
+            }
+            else
+            {
+                if(breakpoints[realThreadName].Remove(address) && address != null)
+                {
+                    cpu.RemoveHook(address.Value, HandleBreakpoint);
+                }
+            }
+        }
+
+        public string[,] GetBreakpoints(string threadName = null)
+        {
+            var entries = breakpoints.SelectMany(t => t.Value, (thread, address) => new { Thread = thread.Key, Address = address });
+            if(threadName != null)
+            {
+                entries = entries.Where(x => x.Thread.Contains(threadName));
+            }
+            var table = new Table().AddRow("Thread", "Address");
+            table.AddRows(entries, x => x.Thread, x => x.Address == null ? "null" : "0x{0:X}".FormatWith(x.Address));
+            return table.ToArray();
+        }
+
+        private void HandleUnknownSyscall(ICpuSupportingGdb _cpu, ulong address)
+        {
+            if(cpu.A[0] != DebugThreadNameSyscall)
+            {
+                return;
+            }
+
+            cpu.RemoveHook(address, HandleUnknownSyscall);
+
+            var ksCurThreadAddress = bus.GetSymbolAddress("ksCurThread");
+            var lookupIPCBufferAddress = bus.GetSymbolAddress("lookupIPCBuffer");
+            var lookupCapAndSlotAddress = bus.GetSymbolAddress("lookupCapAndSlot");
+
+            ksCurThreadPhysAddress = cpu.TranslateAddress(ksCurThreadAddress, MpuAccess.Read);
+            cpu.AddHook(lookupCapAndSlotAddress, HandleLookupCapAndSlotAddress);
+            cpu.AddHook(lookupIPCBufferAddress, HandleLookupIPCBuffer);
+        }
+
+        private void Finalize(ICpuSupportingGdb _cpu, ulong address)
+        {
+            cpu.RemoveHook(address, Finalize);
+            Ready = true;
+            this.Log(LogLevel.Info, "Initialization complete.");
+        }
+
+        private void ksCurThreadChanged(ICpuSupportingGdb _cpu, ulong address)
+        {
+            foreach(var addr in breakpoints.Values.SelectMany(v => v).Where(v => v != null))
+            {
+                cpu.RemoveHook(addr.Value, HandleBreakpoint);
+            }
+
+            var thread = CurrentThread();
+            if(!breakpoints.ContainsKey(thread))
+            {
+                return;
+            }
+
+            var tcb = cpu.TranslateAddress((ulong)bus.ReadDoubleWord(this.ksCurThreadPhysAddress, context: cpu) + 0x88, MpuAccess.Read);
+            var pc = bus.ReadDoubleWord(tcb, context: cpu);
+            cpu.AddHook(pc, HandleThreadSwitch);
+        }
+
+        private void HandleThreadSwitch(ICpuSupportingGdb _cpu, ulong address)
+        {
+            cpu.RemoveHook(address, HandleThreadSwitch);
+
+            var thread = CurrentThread();
+            foreach(var addr in breakpoints[thread])
+            {
+                if(addr == null)
+                {
+                    continue;
+                }
+
+                cpu.AddHook(addr.Value, HandleBreakpoint);
+            }
+
+            if(breakpoints[thread].Contains(null))
+            {
+                cpu.Pause();
+                cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu.Id, address, BreakpointType.MemoryBreakpoint), true);
+            }
+        }
+
+        private void HandleBreakpoint(ICpuSupportingGdb _cpu, ulong address)
+        {
+            cpu.Pause();
+            cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu.Id, address, BreakpointType.MemoryBreakpoint), true);
+        }
+
+        private void HandleLookupCapAndSlotAddress(ICpuSupportingGdb _cpu, ulong address)
+        {
+            cpu.RemoveHook(address, HandleLookupCapAndSlotAddress);
+            cpu.AddHook(cpu.RA, HandlePostLookupCapAndSlotAddress);
+        }
+
+        private void HandlePostLookupCapAndSlotAddress(ICpuSupportingGdb _cpu, ulong address)
+        {
+            var luRet = cpu.A[0];
+            var paddr = cpu.TranslateAddress(luRet + 0x4UL, MpuAccess.Read);
+            var underlying = bus.ReadDoubleWord(paddr, context: cpu);
+            currentTCB = underlying & 0xffffff00;
+        }
+
+
+        private void HandleLookupIPCBuffer(ICpuSupportingGdb _cpu, ulong address)
+        {
+            cpu.RemoveHook(address, HandleLookupIPCBuffer);
+            cpu.AddHook(cpu.RA, HandlePostLookupIPCBuffer);
+        }
+
+        private void HandlePostLookupIPCBuffer(ICpuSupportingGdb _cpu, ulong address)
+        {
+            var paddr = cpu.TranslateAddress(cpu.A[0] + 0x4UL, MpuAccess.Read);
+            // NOTE(mwitkowski): 100 bytes is arbitrary
+            var bytes = bus.ReadBytes(paddr, 100, context: cpu)
+                           .TakeWhile(b => b != 0)
+                           .ToArray();
+            var threadName = System.Text.Encoding.ASCII.GetString(bytes);
+
+            if(!mapping.ContainsKey(currentTCB))
+            {
+                mapping.Add(currentTCB, threadName);
+            }
+            else if(threadName.Contains("_control"))
+            {
+                mapping[currentTCB] = threadName;
+            }
+
+            if(pendingThreadName != null && threadName.Contains(pendingThreadName))
+            {
+                pendingThreadName = null;
+                cpu.Pause();
+                cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu.Id, address, BreakpointType.MemoryBreakpoint), true);
+            }
+        }
+
+        public bool BreakpointsEnabled
+        {
+            get
+            {
+                return breakpointsEnabled;
+            }
+            set
+            {
+                if(breakpointsEnabled == value)
+                {
+                    return;
+                }
+
+                breakpointsEnabled = value;
+                var address = bus.GetSymbolAddress("restore_user_context");
+                if(value)
+                {
+                    cpu.AddHook(address, ksCurThreadChanged);
+                }
+                else
+                {
+                    cpu.RemoveHook(address, ksCurThreadChanged);
+                }
+            }
+        }
+
+        public string[] Threads => mapping.Values.ToArray();
+
+        public bool Ready { get; private set; }
+
+        private const uint DebugThreadNameSyscall = 0xffffffef;
+
+        private readonly Dictionary<string, SortedSet<ulong?>> breakpoints;
+        private readonly Dictionary<ulong, string> mapping;
+        private readonly RiscV32 cpu;
+        private SystemBus bus => cpu.Bus;
+
+        private bool breakpointsEnabled;
+        private ulong ksCurThreadPhysAddress;
+        private string pendingThreadName;
+        private ulong currentTCB;
+    }
+}
+