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; + } +} +