仿真平台内核初版 -tlib库 包含<sparc arm riscv powerPC>
This commit is contained in:
56
tools/sel4_extensions/README.md
Normal file
56
tools/sel4_extensions/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# seL4 extensions for GDB
|
||||
|
||||
Convenience script for GDB, adding additional facilities for
|
||||
debugging seL4 applications.
|
||||
|
||||
## Features
|
||||
|
||||
* Creating and removing breakpoints on address in specific thread,
|
||||
* Creating and removing breakpoint on context-switches between kernel and userspace,
|
||||
* Auto-loading symbols for CAmkES project
|
||||
* Investigating current thread
|
||||
|
||||
## Usage
|
||||
|
||||
Firstly, `seL4Extensions.cs` and kernel symbols have to be loaded in Renode. This can be accomplished by adding following lines to `.resc` file:
|
||||
```
|
||||
include @path/to/seL4Extensions.cs
|
||||
sysbus LoadSymbolsFrom @path/to/kernel.elf
|
||||
```
|
||||
Before simulation is started (using `start` command in Renode), the seL4 extensions have to be loaded on
|
||||
correct CPU, which can be done using `cpu CreateSeL4` command in Renode. Lastly, the Renode-GDB glue has to be sourced in GDB. All those steps can be done in single command:
|
||||
|
||||
```
|
||||
$ gdb-multiarch -ex 'target remote :3333' -ex 'monitor cpu CreateSeL4' \
|
||||
-ex 'source path/to/gdbscript.py' -ex 'monitor start'
|
||||
```
|
||||
|
||||
`start` at the end is optional.
|
||||
In GDB information about commands can be accessed using `info sel4` command.
|
||||
|
||||
## Example
|
||||
|
||||
### Create breakpoint on main function in rootserver thread
|
||||
|
||||
```
|
||||
(gdb) # Wait until rootserver thread is known to seL4 extensions
|
||||
(gdb) sel4 wait-for-thread rootserver
|
||||
(gdb) # Switch symbols to rootserver's
|
||||
(gdb) sel4 switch-symbols rootserver
|
||||
(gdb) # Create temporary breakpoint on main function in rootserver thread
|
||||
(gdb) sel4 tbreak rootserver main
|
||||
(gdb) continue
|
||||
(gdb) # We can confirm, that we are indeed in rootserver thread
|
||||
(gdb) sel4 thread
|
||||
rootserver
|
||||
```
|
||||
|
||||
### Return from userspace to kernel
|
||||
|
||||
```
|
||||
(gdb) # Create temporary breakpoint on context-switch to kernel
|
||||
(gdb) sel4 tbreak kernel
|
||||
(gdb) continue
|
||||
(gdb) sel4 thread
|
||||
kernel
|
||||
```
|
||||
468
tools/sel4_extensions/gdbscript.py
Executable file
468
tools/sel4_extensions/gdbscript.py
Executable file
@@ -0,0 +1,468 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2010-2023 Antmicro
|
||||
#
|
||||
# This file is licensed under the MIT License.
|
||||
# Full license text is available in 'licenses/MIT.txt'.
|
||||
#
|
||||
|
||||
# This script provides convenience functions for debugging user-space seL4 applications.
|
||||
# All of the commands are available under `sel4` prefix, e.g. `sel4 break`, `sel4 thread`, etc.
|
||||
# To use this script in GDB you have to source it first, either using `source` command or running
|
||||
# GDB with `-x <path/to/gdbscript.py>` argument.
|
||||
#
|
||||
# Example: break on main function in rootserver thread
|
||||
#
|
||||
# (gdb) sel4 wait-for-thread rootserver
|
||||
# (gdb) sel4 switch-symbols rootserver
|
||||
# (gdb) sel4 break rootserver main
|
||||
# (gdb) continue
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
from enum import IntEnum
|
||||
from glob import glob
|
||||
import pickle
|
||||
|
||||
|
||||
def get_envvar(name):
|
||||
if name not in os.environ:
|
||||
raise gdb.GdbError("{} environment variable is not set".format(name))
|
||||
else:
|
||||
return os.environ[name]
|
||||
|
||||
|
||||
SYMBOL_AUTOSWITCHING = True
|
||||
SOURCE_DIR = get_envvar('SOURCE_DIR')
|
||||
sys.path.append(os.path.join(SOURCE_DIR, 'projects', 'capdl', 'python-capdl-tool'))
|
||||
|
||||
import capdl
|
||||
|
||||
|
||||
class CapDLSource:
|
||||
def __init__(self, spec_pickle):
|
||||
with open(spec_pickle, 'rb') as f:
|
||||
cdl = pickle.load(f)
|
||||
|
||||
self.threads = {obj.name for obj in cdl.obj_space if isinstance(obj, capdl.TCB)}
|
||||
# Add rootserver thread explicitly
|
||||
self.threads.add('rootserver')
|
||||
|
||||
def get_threads(self):
|
||||
return self.threads
|
||||
|
||||
|
||||
class ExitUserspaceMode(IntEnum):
|
||||
Never = 0
|
||||
Once = 1
|
||||
Always = 2
|
||||
|
||||
|
||||
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 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):
|
||||
"""Continues until given thread is available to make operations on.
|
||||
|
||||
This command waits until seL4_DebugNameThread syscall is handled by the kernel,
|
||||
which allows for the use of break and tbreak commands."""
|
||||
|
||||
def __init__(self):
|
||||
super(self.__class__, self).__init__('sel4 wait-for-thread', gdb.COMMAND_BREAKPOINTS)
|
||||
|
||||
def complete(self, text, word):
|
||||
global THREADS
|
||||
return list(component for component in THREADS if component.startswith(text))
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
threads = get_threads()
|
||||
thread_exists = next((thread for thread in threads if arg in thread), None)
|
||||
if thread_exists is not None:
|
||||
raise gdb.GdbError("'{}' thread is already loaded".format(thread_exists))
|
||||
|
||||
gdb.execute('mon seL4 BreakOnNamingThread "{}"'.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 component 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
|
||||
|
||||
# List all known components, add 'kernel' as an option for DEFAULT_BINARY
|
||||
components = list(BINARIES.keys())
|
||||
components.append('kernel')
|
||||
return list(component for component in components if component.startswith(text))
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
if not arg:
|
||||
thread = get_current_thread()
|
||||
switch_symbols(thread, verbose=True)
|
||||
elif arg == 'kernel':
|
||||
switch_symbols(verbose=True)
|
||||
else:
|
||||
switch_symbols(arg, fallback=False, verbose=True)
|
||||
|
||||
|
||||
def _sel4_break_complete_helper(text, word):
|
||||
words = text.split(' ')
|
||||
if not text or len(words) <= 1:
|
||||
threads = get_threads()
|
||||
# Extend list of threads with additional special names
|
||||
threads.extend(['user', 'kernel'])
|
||||
return list(thread for thread in threads if thread.startswith(word))
|
||||
elif len(words) == 2:
|
||||
return gdb.COMPLETE_SYMBOL
|
||||
else:
|
||||
return gdb.COMPLETE_NONE
|
||||
|
||||
|
||||
def _sel4_break_invoke_helper(command, arg):
|
||||
args = arg.split(' ')
|
||||
|
||||
if command == 'break':
|
||||
exit_userspace_mode = ExitUserspaceMode.Always
|
||||
monitor_command = 'mon seL4 SetBreakpoint'
|
||||
elif command == 'tbreak':
|
||||
exit_userspace_mode = ExitUserspaceMode.Once
|
||||
monitor_command = 'mon seL4 SetTemporaryBreakpoint'
|
||||
else:
|
||||
raise ValueError("'{}' is invalid value for command".format(command))
|
||||
|
||||
if len(args) < 1 or not args[0]:
|
||||
raise gdb.GdbError("sel4 {}: thread argument is required".format(command))
|
||||
|
||||
thread = args[0]
|
||||
symbol_name = args[1] if len(args) >= 2 else None
|
||||
|
||||
if thread == 'kernel':
|
||||
gdb.execute('mon seL4 BreakOnExittingUserspace {}'.format(exit_userspace_mode))
|
||||
return
|
||||
|
||||
if thread == 'user':
|
||||
thread = '<any>'
|
||||
|
||||
if symbol_name:
|
||||
_, hits = gdb.decode_line(symbol_name)
|
||||
for hit in hits:
|
||||
gdb.execute('{} "{}" {}'.format(monitor_command, thread, hex(hit.pc)))
|
||||
else:
|
||||
gdb.execute('{} "{}"'.format(monitor_command, thread))
|
||||
|
||||
|
||||
@source
|
||||
class seL4Break(gdb.Command):
|
||||
"""Creates a breakpoint
|
||||
|
||||
sel4 break thread [address]
|
||||
|
||||
Creates a 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):
|
||||
return _sel4_break_complete_helper(text, word)
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
_sel4_break_invoke_helper('break', arg)
|
||||
|
||||
|
||||
@source
|
||||
class seL4TBreak(gdb.Command):
|
||||
"""Creates temporary breakpoint
|
||||
|
||||
sel4 tbreak thread [address]
|
||||
|
||||
Creates temporary 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 tbreak', gdb.COMMAND_BREAKPOINTS)
|
||||
|
||||
def complete(self, text, word):
|
||||
return _sel4_break_complete_helper(text, word)
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
_sel4_break_invoke_helper('tbreak', arg)
|
||||
|
||||
|
||||
@source
|
||||
class seL4Delete(gdb.Command):
|
||||
"""Removes a 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:
|
||||
threads = get_threads()
|
||||
# Extend list of threads with additional special names
|
||||
threads.extend(['user', 'kernel'])
|
||||
return list(thread for thread in threads if thread.startswith(word))
|
||||
elif len(words) == 2:
|
||||
# Renode returns breakpoints as a table, so we have to parse it
|
||||
# in order to gather completion list
|
||||
chosen_thread = words[0]
|
||||
if chosen_thread == 'user':
|
||||
chosen_thread = '<any>'
|
||||
completions = []
|
||||
for thread, address in get_breakpoints(chosen_thread):
|
||||
if chosen_thread not in thread:
|
||||
continue
|
||||
if address == '<any>' or not address.startswith(word):
|
||||
continue
|
||||
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 thread == 'kernel':
|
||||
gdb.execute('mon seL4 BreakOnExittingUserspace {}'.format(ExitUserspaceMode.Never))
|
||||
return
|
||||
|
||||
if thread == 'user':
|
||||
thread = '<any>'
|
||||
|
||||
if address:
|
||||
gdb.execute('mon seL4 RemoveBreakpoint "{}" {}'.format(thread, address))
|
||||
gdb.execute('mon seL4 RemoveTemporaryBreakpoint "{}" {}'.format(thread, address))
|
||||
else:
|
||||
gdb.execute('mon seL4 RemoveBreakpoint "{}"'.format(thread))
|
||||
gdb.execute('mon seL4 RemoveTemporaryBreakpoint "{}"'.format(thread))
|
||||
|
||||
|
||||
@source
|
||||
class seL4DeleteAll(gdb.Command):
|
||||
"""Removes all breakpoints assigned to the 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 BreakOnExittingUserspace {}'.format(ExitUserspaceMode.Never))
|
||||
if arg:
|
||||
gdb.execute('mon seL4 RemoveAllBreakpoints "{}"'.format(arg))
|
||||
else:
|
||||
gdb.execute('mon seL4 RemoveAllBreakpoints')
|
||||
|
||||
|
||||
@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):
|
||||
if not arg:
|
||||
gdb.execute('mon seL4 GetBreakpoints')
|
||||
else:
|
||||
gdb.execute('mon seL4 GetBreakpoints "{}"'.format(arg))
|
||||
|
||||
|
||||
@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
|
||||
class seL4SymbolAutoswitching(gdb.Command):
|
||||
"""Enables or disables symbol file autoswitching"""
|
||||
|
||||
def __init__(self):
|
||||
super(self.__class__, self).__init__('sel4 symbol-autoswitching', gdb.COMMAND_USER)
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
global SYMBOL_AUTOSWITCHING
|
||||
|
||||
if not arg:
|
||||
print('Symbol autoswitching is {}'.format('enabled' if SYMBOL_AUTOSWITCHING else 'disabled'))
|
||||
return
|
||||
|
||||
if arg.isdigit():
|
||||
arg = bool(int(arg))
|
||||
elif arg in ['True', 'true']:
|
||||
arg = True
|
||||
elif arg in ['False', 'false']:
|
||||
arg = False
|
||||
else:
|
||||
raise gdb.GdbError('{} is invalid argument for sel4 symbol-autoswitching'.format(arg))
|
||||
|
||||
SYMBOL_AUTOSWITCHING = arg
|
||||
print('Symbol autoswitching is now {}'.format('enabled' if SYMBOL_AUTOSWITCHING else 'disabled'))
|
||||
|
||||
|
||||
@source
|
||||
def find_binaries():
|
||||
global BINARIES, DEFAULT_BINARY
|
||||
BINARIES = {}
|
||||
|
||||
build_dir = get_envvar('BUILD_DIR')
|
||||
# Default binary is kernel, because every other component has its own thread,
|
||||
# including rootserver.
|
||||
DEFAULT_BINARY = os.path.join(build_dir, 'kernel', 'kernel.elf')
|
||||
|
||||
# Find all CAmkES components
|
||||
images_path = os.path.join(build_dir, '*.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
|
||||
|
||||
# rootserver thread is just capdl-loader
|
||||
BINARIES['rootserver'] = os.path.join(build_dir, 'capdl-loader')
|
||||
|
||||
|
||||
@source
|
||||
def find_threads():
|
||||
global THREADS
|
||||
|
||||
build_dir = get_envvar('BUILD_DIR')
|
||||
cdl_pickle = os.path.join(build_dir, 'object.pickle')
|
||||
provider = CapDLSource(cdl_pickle)
|
||||
THREADS = provider.get_threads()
|
||||
|
||||
|
||||
def get_breakpoints(thread=None):
|
||||
if not thread:
|
||||
breakpoints = gdb.execute('mon seL4 GetBreakpointsPlain', to_string=True).strip()
|
||||
else:
|
||||
breakpoints = gdb.execute('mon seL4 GetBreakpointsPlain "{}"'.format(thread), to_string=True).strip()
|
||||
return [tuple(line.strip().split(':')) for line in breakpoints.splitlines()]
|
||||
|
||||
|
||||
def get_current_thread():
|
||||
return gdb.execute('mon seL4 CurrentThread', to_string=True).strip()
|
||||
|
||||
|
||||
def get_threads():
|
||||
command_output = gdb.execute('mon seL4 Threads', to_string=True)
|
||||
threads = []
|
||||
for line in command_output.strip().split('\n')[1:-1]:
|
||||
splitted_line = (thread.strip() for thread in line.split(','))
|
||||
threads.extend(thread for thread in splitted_line if thread)
|
||||
return threads
|
||||
|
||||
|
||||
def resolve_symbol_file(thread, fallback=True):
|
||||
if thread is None:
|
||||
image_path = DEFAULT_BINARY
|
||||
else:
|
||||
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:
|
||||
return None
|
||||
return image_path
|
||||
|
||||
|
||||
def switch_symbols(thread=None, fallback=True, verbose=False):
|
||||
"""Switch symbol-file to the file corresponding to the given thread
|
||||
|
||||
If fallback is set to true and the given thread is not found, it will set
|
||||
symbol-file to DEFAULT_BINARY (def. kernel.elf).
|
||||
If verbose is set to true, symbol-file command will be run in interactive
|
||||
mode and information of switching symbol file will be shown"""
|
||||
global BINARIES, DEFAULT_BINARY
|
||||
|
||||
image_path = resolve_symbol_file(thread, fallback)
|
||||
|
||||
if image_path is None:
|
||||
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
|
||||
|
||||
global SYMBOL_AUTOSWITCHING
|
||||
|
||||
if SYMBOL_AUTOSWITCHING:
|
||||
thread = get_current_thread()
|
||||
switch_symbols(thread)
|
||||
|
||||
gdb.events.stop.connect(stop_handler)
|
||||
651
tools/sel4_extensions/seL4Extensions.cs
Normal file
651
tools/sel4_extensions/seL4Extensions.cs
Normal file
@@ -0,0 +1,651 @@
|
||||
//
|
||||
// Copyright (c) 2010-2024 Antmicro
|
||||
//
|
||||
// This file is licensed under the MIT License.
|
||||
// Full license text is available in 'licenses/MIT.txt'.
|
||||
//
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using Antmicro.Renode.Core;
|
||||
using Antmicro.Renode.Logging;
|
||||
using Antmicro.Renode.Exceptions;
|
||||
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 ICpuSupportingGdb @this, ulong? debugThreadNameSyscallId = null)
|
||||
{
|
||||
EmulationManager.Instance.CurrentEmulation.ExternalsManager.AddExternal(new SeL4DebugHelper(@this, debugThreadNameSyscallId), "seL4");
|
||||
}
|
||||
}
|
||||
|
||||
public class SeL4DebugHelper : IExternal
|
||||
{
|
||||
public SeL4DebugHelper(ICpuSupportingGdb cpu, ulong? debugThreadNameSyscallId)
|
||||
{
|
||||
if(cpu is Arm)
|
||||
{
|
||||
this.callingConvention = new ArmCallingConvention(cpu);
|
||||
}
|
||||
else if(cpu is RiscV32)
|
||||
{
|
||||
this.callingConvention = new RiscVCallingConvention(cpu);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RecoverableException("Only ARM and RV32 based platforms are supported by the seL4 extension");
|
||||
}
|
||||
|
||||
this.debugThreadNameSyscall = debugThreadNameSyscallId ?? DefaultDebugThreadNameSyscall;
|
||||
|
||||
this.cpu = cpu;
|
||||
this.mapping = new Dictionary<ulong, string>();
|
||||
this.breakpoints = new Dictionary<ulong, HashSet<string>>();
|
||||
this.temporaryBreakpoints = new Dictionary<ulong, HashSet<string>>();
|
||||
|
||||
// Save restore_user_context as we will be using it pretty often
|
||||
this.restoreUserContextAddress = cpu.Bus.GetSymbolAddress("restore_user_context");
|
||||
|
||||
// handleUnknownSyscall function is handling seL4_DebugThreadName syscall.
|
||||
// We are using this hook to inspect thread's TCB after it was initialized
|
||||
var handleUnknownSyscallAddress = cpu.Bus.GetSymbolAddress("handleUnknownSyscall");
|
||||
this.cpu.AddHook(handleUnknownSyscallAddress, HandleUnknownSyscall);
|
||||
// When everything is set up and none of threads is working, this function will be called
|
||||
// It seems to be always called after initialization of all CAmkES components
|
||||
// so we can use it to check "readiness".
|
||||
var idleThreadAddresss = cpu.Bus.GetSymbolAddress("idle_thread");
|
||||
this.cpu.AddHook(idleThreadAddresss, Finalize);
|
||||
}
|
||||
|
||||
public string CurrentThread()
|
||||
{
|
||||
if(callingConvention.PrivilegeMode == PrivilegeMode.Supervisor)
|
||||
{
|
||||
return "kernel";
|
||||
}
|
||||
return CurrentThreadUnsafe();
|
||||
}
|
||||
|
||||
public void BreakOnNamingThread(string threadName)
|
||||
{
|
||||
pendingThreadName = threadName;
|
||||
}
|
||||
|
||||
public void BreakOnExittingUserspace(ExitUserspaceMode mode)
|
||||
{
|
||||
if(mode == exitUserspaceMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(exitUserspaceMode == ExitUserspaceMode.Never)
|
||||
{
|
||||
cpu.AddHook(callingConvention.SyscallTrapAddress, HandleExitUserspace);
|
||||
}
|
||||
else if(mode == ExitUserspaceMode.Never)
|
||||
{
|
||||
cpu.RemoveHook(callingConvention.SyscallTrapAddress, HandleExitUserspace);
|
||||
}
|
||||
|
||||
exitUserspaceMode = mode;
|
||||
}
|
||||
|
||||
// Sets the breakpoint on given address in chosen thread
|
||||
// If address is not given, the breakpoint is set right after
|
||||
// on the first instruction after context switch
|
||||
public void SetBreakpoint(string threadName, ulong address = WildcardAddress)
|
||||
{
|
||||
SetBreakpointHelper(threadName, address, breakpoints);
|
||||
}
|
||||
|
||||
// Similiar to SetBreakpoint, but for temporary breakpoints
|
||||
public void SetTemporaryBreakpoint(string threadName, ulong address = WildcardAddress)
|
||||
{
|
||||
SetBreakpointHelper(threadName, address, temporaryBreakpoints);
|
||||
}
|
||||
|
||||
// Removes existing breakpoint on given address in chosen thread
|
||||
// If address is not given, then breakpoint which happens on context switch
|
||||
// is removed (see SetBreakpoint). If removeAll is set to true, all breakpoints for
|
||||
// given thread are removed.
|
||||
public void RemoveBreakpoint(string threadName, ulong address = WildcardAddress)
|
||||
{
|
||||
RemoveBreakpointHelper(threadName, address, breakpoints);
|
||||
}
|
||||
|
||||
public void RemoveTemporaryBreakpoint(string threadName, ulong address = WildcardAddress)
|
||||
{
|
||||
RemoveBreakpointHelper(threadName, address, temporaryBreakpoints);
|
||||
}
|
||||
|
||||
public void RemoveAllBreakpoints(string threadName = null)
|
||||
{
|
||||
string realThreadName = null;
|
||||
if(threadName != null && !TryGetRealThreadName(threadName, out realThreadName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(var item in breakpoints.ToList())
|
||||
{
|
||||
if(realThreadName != null)
|
||||
{
|
||||
item.Value.Remove(realThreadName);
|
||||
}
|
||||
if(realThreadName == null || item.Value.Count == 0)
|
||||
{
|
||||
breakpoints.Remove(item.Key);
|
||||
}
|
||||
if(GetBreakpointsCount(item.Key) == 0)
|
||||
{
|
||||
RemoveHook(item.Key);
|
||||
}
|
||||
}
|
||||
foreach(var item in temporaryBreakpoints.ToList())
|
||||
{
|
||||
if(realThreadName != null)
|
||||
{
|
||||
item.Value.Remove(realThreadName);
|
||||
}
|
||||
if(realThreadName == null || item.Value.Count == 0)
|
||||
{
|
||||
temporaryBreakpoints.Remove(item.Key);
|
||||
}
|
||||
if(GetBreakpointsCount(item.Key) == 0)
|
||||
{
|
||||
RemoveHook(item.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns table with all the breakpoints. If threadName is set,
|
||||
// returns only breakpoints set in given thread.
|
||||
public string[,] GetBreakpoints(string threadName = null)
|
||||
{
|
||||
var entries = breakpoints.SelectMany(t => t.Value, (entry, thread) => new { Thread = thread, Address = entry.Key, Temporary = false })
|
||||
.Concat(temporaryBreakpoints.SelectMany(t => t.Value, (entry, thread) => new { Thread = thread, Address = entry.Key, Temporary = true }));
|
||||
|
||||
if(threadName != null)
|
||||
{
|
||||
entries = entries.Where(x => x.Thread.Contains(threadName));
|
||||
}
|
||||
var table = new Table().AddRow("Thread", "Address", "Temporary");
|
||||
table.AddRows(entries,
|
||||
x => x.Thread == AnyThreadName ? "any" : x.Thread,
|
||||
x => x.Address == WildcardAddress ? "any" : "0x{0:X}".FormatWith(x.Address),
|
||||
x => x.Temporary.ToString());
|
||||
if(exitUserspaceMode != ExitUserspaceMode.Never)
|
||||
{
|
||||
table.AddRow("kernel", "any", (exitUserspaceMode == ExitUserspaceMode.Once).ToString());
|
||||
}
|
||||
return table.ToArray();
|
||||
}
|
||||
|
||||
// Returns list of all the breakpoints in script-friendly format: <THREAD_NAME>:<ADDRESS>\n.
|
||||
// If threadName is set, returns only breakpoints set in given thread.
|
||||
public string GetBreakpointsPlain(string threadName = null)
|
||||
{
|
||||
var entries = breakpoints.SelectMany(t => t.Value, (entry, thread) => new { Thread = thread, Address = entry.Key })
|
||||
.Concat(temporaryBreakpoints.SelectMany(t => t.Value, (entry, thread) => new { Thread = thread, Address = entry.Key }));
|
||||
|
||||
if(threadName != null)
|
||||
{
|
||||
entries = entries.Where(x => x.Thread.Contains(threadName));
|
||||
}
|
||||
var output = entries.Select(entry => "{0}:{1}".FormatWith(
|
||||
entry.Thread,
|
||||
entry.Address == WildcardAddress ? "any" : "0x{0:X}".FormatWith(entry.Address)));
|
||||
return string.Join("\n", output);
|
||||
}
|
||||
|
||||
public string[] Threads => mapping.Values.ToArray();
|
||||
|
||||
public bool Ready { get; private set; }
|
||||
|
||||
private ulong TryTranslateAddress(ICpuSupportingGdb cpu, ulong virtualAddress)
|
||||
{
|
||||
if(cpu is ICPUWithMMU cpuWithMmu)
|
||||
{
|
||||
virtualAddress = cpuWithMmu.TranslateAddress(virtualAddress, MpuAccess.Read);
|
||||
}
|
||||
return virtualAddress;
|
||||
}
|
||||
|
||||
private void HandleUnknownSyscall(ICpuSupportingGdb cpu, ulong address)
|
||||
{
|
||||
// Check if seL4_DebugThreadName was called
|
||||
if((callingConvention.FirstArgument & 0xFFFFFFFF) != debugThreadNameSyscall)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We are in seL4_DebugThreadName handler, we don't need this hook anymore
|
||||
cpu.RemoveHook(address, HandleUnknownSyscall);
|
||||
|
||||
// This function will now call lookupIPCBuffer and lookupCapAndSlot
|
||||
// We can temporarily hook those functions, and save theirs
|
||||
// return addresses (which will be somewhere in handleUnknownSyscall)
|
||||
// so we can use them later to "scrape" thread information.
|
||||
// Additionally, we are getting address of ksCurThread variable
|
||||
// which stores address of TCB of current thread.
|
||||
var ksCurThreadAddress = cpu.Bus.GetSymbolAddress("ksCurThread");
|
||||
var lookupIPCBufferAddress = cpu.Bus.GetSymbolAddress("lookupIPCBuffer");
|
||||
var lookupCapAndSlotAddress = cpu.Bus.GetSymbolAddress("lookupCapAndSlot");
|
||||
|
||||
// At this point we are sure, that we are in kernel context and ksCurrThread symbol vaddr
|
||||
// will resolve properly. Therefore we can translate virtual address to physical address
|
||||
// and use it to read memory. That allow us to check current TCB no matter in which
|
||||
// context/privilege mode we are currently in, ignoring MMU completely.
|
||||
ksCurThreadPhysAddress = TryTranslateAddress(cpu, ksCurThreadAddress);
|
||||
|
||||
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 HandleRestoreUserContext(ICpuSupportingGdb cpu, ulong address)
|
||||
{
|
||||
var threadName = CurrentThreadUnsafe();
|
||||
if(!DoBreakpointExists(WildcardAddress, threadName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong tcbAddress = cpu.Bus.ReadDoubleWord(this.ksCurThreadPhysAddress, context: cpu);
|
||||
if(!IsValidAddress(tcbAddress))
|
||||
{
|
||||
this.Log(LogLevel.Debug, "Got invalid address for TCB, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
var nextPCAddress = TryTranslateAddress(cpu, tcbAddress + callingConvention.TCBNextPCOffset);
|
||||
|
||||
if(!IsValidAddress(nextPCAddress))
|
||||
{
|
||||
this.Log(LogLevel.Debug, "NextPC address in TCB is invalid, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
var pc = cpu.Bus.ReadDoubleWord(nextPCAddress, context: cpu);
|
||||
cpu.AddHook(pc, HandleThreadSwitch);
|
||||
}
|
||||
|
||||
private void HandleThreadSwitch(ICpuSupportingGdb cpu, ulong address)
|
||||
{
|
||||
var threadName = CurrentThread();
|
||||
// Remove temporary breakpoint if exists
|
||||
ClearTemporaryBreakpoint(WildcardAddress, threadName);
|
||||
// We changed context, remove this hook as we don't need it anymore
|
||||
cpu.RemoveHook(address, HandleThreadSwitch);
|
||||
cpu.Pause();
|
||||
cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, address, BreakpointType.MemoryBreakpoint));
|
||||
}
|
||||
|
||||
private void HandleBreakpoint(ICpuSupportingGdb cpu, ulong address)
|
||||
{
|
||||
var threadName = CurrentThread();
|
||||
if(!DoBreakpointExists(address, threadName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearTemporaryBreakpoint(address, threadName);
|
||||
cpu.Pause();
|
||||
cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, address, BreakpointType.MemoryBreakpoint));
|
||||
}
|
||||
|
||||
private void HandleExitUserspace(ICpuSupportingGdb cpu, ulong address)
|
||||
{
|
||||
if(callingConvention.PrivilegeMode != PrivilegeMode.Supervisor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cpu.Pause();
|
||||
cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, address, BreakpointType.MemoryBreakpoint));
|
||||
if(exitUserspaceMode == ExitUserspaceMode.Once)
|
||||
{
|
||||
cpu.RemoveHook(address, HandleExitUserspace);
|
||||
exitUserspaceMode = ExitUserspaceMode.Never;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleLookupCapAndSlotAddress(ICpuSupportingGdb cpu, ulong address)
|
||||
{
|
||||
// Save address to instruction in handleUnknownSyscall after call to lookupCapAndSlot
|
||||
cpu.RemoveHook(address, HandleLookupCapAndSlotAddress);
|
||||
cpu.AddHook(callingConvention.ReturnAddress, HandlePostLookupCapAndSlotAddress);
|
||||
}
|
||||
|
||||
private void HandlePostLookupCapAndSlotAddress(ICpuSupportingGdb cpu, ulong address)
|
||||
{
|
||||
// Return value of lookupCapAndSlot is a structure
|
||||
// with size of two machine words. We are interested in second value
|
||||
// which is address of the capability (in this case TCB)
|
||||
var luRet = callingConvention.ReturnValue;
|
||||
var paddr = TryTranslateAddress(cpu, luRet + 0x4UL);
|
||||
var underlying = cpu.Bus.ReadDoubleWord(paddr, context: cpu);
|
||||
currentTCB = underlying & 0xffffffffffffff00;
|
||||
}
|
||||
|
||||
private void HandleLookupIPCBuffer(ICpuSupportingGdb cpu, ulong address)
|
||||
{
|
||||
// Save address to instruction in handleUnknownSyscall after call to lookupIPCBuffer
|
||||
cpu.RemoveHook(address, HandleLookupIPCBuffer);
|
||||
cpu.AddHook(callingConvention.ReturnAddress, HandlePostLookupIPCBuffer);
|
||||
}
|
||||
|
||||
private void HandlePostLookupIPCBuffer(ICpuSupportingGdb cpu, ulong address)
|
||||
{
|
||||
// In A0 register address to IPC buffer is returned.
|
||||
// As seL4_DebugThreadName saves pointer to the string in IPC buffer,
|
||||
// we can now just recover and read it.
|
||||
var paddr = TryTranslateAddress(cpu, callingConvention.ReturnValue + 0x4UL);
|
||||
var buffer = new List<byte>();
|
||||
|
||||
// Maximum string size is MaximumMesageLength * size of machine word - 1
|
||||
for(ulong i = 0; i < MaximumMessageLength * 4 - 1; ++i)
|
||||
{
|
||||
var c = cpu.Bus.ReadByte(paddr + i, context: cpu);
|
||||
if(c == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
buffer.Add(c);
|
||||
}
|
||||
|
||||
var threadName = System.Text.Encoding.ASCII.GetString(buffer.ToArray());
|
||||
|
||||
// This function is called _after_ lookupCapAndSlot, therefore we now
|
||||
// have both TCB address and thread's name. We can add it to our list
|
||||
// of known threads.
|
||||
if(!mapping.ContainsKey(currentTCB) || threadName.Contains("_control"))
|
||||
{
|
||||
mapping[currentTCB] = threadName;
|
||||
}
|
||||
|
||||
// There was pendingThreadName set by WaitForThread function. As we have now all
|
||||
// necessary information for requested thread, we can enter SingleStepMode
|
||||
// (and thus return to prompt in GDB) so user can do something with it,
|
||||
// e.g. create breakpoint on this thread.
|
||||
if(pendingThreadName != null && threadName.Contains(pendingThreadName))
|
||||
{
|
||||
pendingThreadName = null;
|
||||
cpu.Pause();
|
||||
cpu.EnterSingleStepModeSafely(new HaltArguments(HaltReason.Breakpoint, cpu, address, BreakpointType.MemoryBreakpoint));
|
||||
}
|
||||
}
|
||||
|
||||
private int GetBreakpointsCount(ulong address)
|
||||
{
|
||||
breakpoints.TryGetValue(address, out var bp);
|
||||
temporaryBreakpoints.TryGetValue(address, out var tbp);
|
||||
return (bp?.Count ?? 0) + (tbp?.Count ?? 0);
|
||||
}
|
||||
|
||||
private bool TryGetRealThreadName(string threadName, out string realThreadName)
|
||||
{
|
||||
if(threadName == AnyThreadName)
|
||||
{
|
||||
realThreadName = AnyThreadName;
|
||||
return true;
|
||||
}
|
||||
|
||||
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 false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool DoBreakpointExists(ulong address, string threadName)
|
||||
{
|
||||
return (breakpoints.TryGetValue(address, out var bpList) && (bpList.Contains(AnyThreadName) || bpList.Contains(threadName))) ||
|
||||
(temporaryBreakpoints.TryGetValue(address, out var tbpList) && (tbpList.Contains(AnyThreadName) || tbpList.Contains(threadName)));
|
||||
}
|
||||
|
||||
private void AddContextSwitchHook()
|
||||
{
|
||||
cpu.AddHook(restoreUserContextAddress, HandleRestoreUserContext);
|
||||
}
|
||||
|
||||
private void RemoveContextSwitchHook()
|
||||
{
|
||||
cpu.RemoveHook(restoreUserContextAddress, HandleRestoreUserContext);
|
||||
}
|
||||
|
||||
private void ClearTemporaryBreakpoint(ulong address, string threadName)
|
||||
{
|
||||
if(!temporaryBreakpoints.ContainsKey(address))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
temporaryBreakpoints[address].Remove(threadName);
|
||||
temporaryBreakpoints[address].Remove(AnyThreadName);
|
||||
|
||||
if(GetBreakpointsCount(address) == 0)
|
||||
{
|
||||
RemoveHook(address);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetBreakpointHelper(string threadName, ulong address, Dictionary<ulong, HashSet<string>> breakpointsSource)
|
||||
{
|
||||
if(!TryGetRealThreadName(threadName, out var realThreadName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!breakpointsSource.ContainsKey(address))
|
||||
{
|
||||
breakpointsSource.Add(address, new HashSet<string>());
|
||||
}
|
||||
|
||||
if(!breakpointsSource[address].Add(realThreadName))
|
||||
{
|
||||
this.Log(LogLevel.Warning, "This breakpoint already exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
var breakpointsNum = GetBreakpointsCount(address);
|
||||
|
||||
// Ignore if we already registered breakpoint for this address
|
||||
if(breakpointsNum != 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddHook(address);
|
||||
}
|
||||
|
||||
private void RemoveBreakpointHelper(string threadName, ulong address, Dictionary<ulong, HashSet<string>> breakpointsSource)
|
||||
{
|
||||
if(!breakpointsSource.TryGetValue(address, out var breakpoint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!TryGetRealThreadName(threadName, out var realThreadName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
breakpoint.Remove(realThreadName);
|
||||
var breakpointsNum = GetBreakpointsCount(address);
|
||||
if(breakpointsNum != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveHook(address);
|
||||
}
|
||||
|
||||
private void AddHook(ulong address)
|
||||
{
|
||||
if(address != WildcardAddress)
|
||||
{
|
||||
cpu.AddHook(address, HandleBreakpoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddContextSwitchHook();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveHook(ulong address)
|
||||
{
|
||||
if(address != WildcardAddress)
|
||||
{
|
||||
cpu.RemoveHook(address, HandleBreakpoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveContextSwitchHook();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidAddress(ulong address)
|
||||
{
|
||||
return !(address == 0x00000000 || address == 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
private string CurrentThreadUnsafe()
|
||||
{
|
||||
var tcb = cpu.Bus.ReadDoubleWord(this.ksCurThreadPhysAddress, context: cpu);
|
||||
if(mapping.ContainsKey(tcb))
|
||||
{
|
||||
return mapping[tcb];
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
public enum ExitUserspaceMode
|
||||
{
|
||||
Never,
|
||||
Once,
|
||||
Always,
|
||||
}
|
||||
|
||||
private const uint DefaultDebugThreadNameSyscall = 0xfffffff2;
|
||||
private const string AnyThreadName = "<any>";
|
||||
private const uint WildcardAddress = 0x00000000;
|
||||
private const uint MaximumMessageLength = 120;
|
||||
|
||||
private readonly Dictionary<ulong, HashSet<string>> breakpoints;
|
||||
private readonly Dictionary<ulong, HashSet<string>> temporaryBreakpoints;
|
||||
private readonly Dictionary<ulong, string> mapping;
|
||||
private readonly ICpuSupportingGdb cpu;
|
||||
private readonly ICallingConvention callingConvention;
|
||||
private readonly ulong debugThreadNameSyscall;
|
||||
|
||||
private ExitUserspaceMode exitUserspaceMode;
|
||||
private bool breakpointsEnabled;
|
||||
private ulong ksCurThreadPhysAddress;
|
||||
private ulong restoreUserContextAddress;
|
||||
private string pendingThreadName;
|
||||
private ulong currentTCB;
|
||||
|
||||
private interface ICallingConvention
|
||||
{
|
||||
ulong FirstArgument { get; }
|
||||
ulong ReturnValue { get; }
|
||||
ulong ReturnAddress { get; }
|
||||
ulong SyscallTrapAddress { get; }
|
||||
ulong TCBNextPCOffset { get; }
|
||||
PrivilegeMode PrivilegeMode { get; }
|
||||
}
|
||||
|
||||
private enum PrivilegeMode
|
||||
{
|
||||
Userspace,
|
||||
Supervisor,
|
||||
Other,
|
||||
}
|
||||
|
||||
private class RiscVCallingConvention : ICallingConvention
|
||||
{
|
||||
public RiscVCallingConvention(ICpuSupportingGdb cpu)
|
||||
{
|
||||
this.cpu = cpu;
|
||||
// Assumes that symbols for kernel are loaded
|
||||
syscallTrapAddress = cpu.Bus.GetSymbolAddress("trap_entry");
|
||||
}
|
||||
|
||||
public ulong FirstArgument => cpu.A[0];
|
||||
public ulong ReturnValue => cpu.A[0];
|
||||
public ulong ReturnAddress => cpu.RA;
|
||||
public ulong SyscallTrapAddress => syscallTrapAddress;
|
||||
public PrivilegeMode PrivilegeMode
|
||||
{
|
||||
get
|
||||
{
|
||||
switch((byte)cpu.PRIV)
|
||||
{
|
||||
case 0b00:
|
||||
return PrivilegeMode.Userspace;
|
||||
case 0b01:
|
||||
return PrivilegeMode.Supervisor;
|
||||
default:
|
||||
return PrivilegeMode.Other;
|
||||
}
|
||||
}
|
||||
}
|
||||
public ulong TCBNextPCOffset => 34 * 4;
|
||||
|
||||
private readonly ulong syscallTrapAddress;
|
||||
private readonly dynamic cpu;
|
||||
}
|
||||
|
||||
private class ArmCallingConvention : ICallingConvention
|
||||
{
|
||||
public ArmCallingConvention(ICpuSupportingGdb cpu)
|
||||
{
|
||||
this.cpu = (Arm)cpu;
|
||||
// Assumes that symbols for kernel are loaded
|
||||
syscallTrapAddress = cpu.Bus.GetSymbolAddress("arm_swi_syscall");
|
||||
}
|
||||
|
||||
public ulong FirstArgument => cpu.R[0];
|
||||
public ulong ReturnValue => cpu.R[0];
|
||||
public ulong ReturnAddress => cpu.R[14];
|
||||
public ulong SyscallTrapAddress => syscallTrapAddress;
|
||||
public PrivilegeMode PrivilegeMode
|
||||
{
|
||||
get
|
||||
{
|
||||
switch(cpu.CPSR & 0xfUL)
|
||||
{
|
||||
case 0b00:
|
||||
return PrivilegeMode.Userspace;
|
||||
case 0b11:
|
||||
return PrivilegeMode.Supervisor;
|
||||
default:
|
||||
return PrivilegeMode.Other;
|
||||
}
|
||||
}
|
||||
}
|
||||
public ulong TCBNextPCOffset => 15 * 4;
|
||||
|
||||
private readonly ulong syscallTrapAddress;
|
||||
private readonly Arm cpu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user