vxkernel - v0.7.0-1 : architecture overhaul + bootloader KASLR resolve

---

The objective of this early rework of the kernel architecture is to prepare the
project to run on other hardware than the Casio calculator (like Raspberry Pi
devices). This is why I switched the kernel workflow to integrate a custom
bootloader for the particular kernel image we generate.

The v0.7.0 of the kernel is not released since the USB driver is not stable
yet. But the need to deeply rework the architecture is prioritized. So, as this
is a critical update, I update the minor version (which is a major indicator
here since I don't have the mature version yet (v1.x.x)).

Also, I work on a brand-new micro-kernel (for the Raspberry Pi 3b and Google
Pixels), so, the name of this project, Vhex, has migrated to a temporary
name(?): VxGOS (for Vhex Gaming Operating System), which will be changed as
soon as the v0.8.0 is released. (I choose this name only because the word
"gaming" make me laugh. Take showers folks).

The objective of version 0.8.0 is to support:

    - architecture rework
    - internal bootloader
    - proper KASLR support
    - stack protection (libSSP)

---

*update*
> [common]
  | [kernel] move kernel source file in the new "vxgos" root directory
  | [boards] move board-related files / scripts in the new "vxgos"
  | [bootloader] support fxcg50 self-translation from ROM to RAM
  | [bootloader] support fxcg50 KASLR patching
  | [scripts] move `vxdev` tool in the new "scripts" root directory
  | [scripts] move norm related files to "checker" directory
  | [vxsdk] update the vxSDK indication file
This commit is contained in:
Yann MAGNIN 2023-04-13 13:58:40 +02:00
parent 18e895dfb7
commit 597b25682a
230 changed files with 1472 additions and 33 deletions

View File

@ -11,8 +11,9 @@ autocmd BufWinLeave * call clearmatches()
" - we setup cpp because header file is considered ad C++ file
" - we force sh-elf-vhex for assembly file only
let g:ale_asm_gcc_executable = 'sh-elf-vhex-gcc'
let g:ale_c_cc_options = '-std=c11 -Wall -D_GNU_SOURCE -D_DEFAULT_SOURCE -I./kernel/include -I./boards/*/include'
let g:ale_cpp_cc_options = '-std=c11 -Wall -I./kernel/include -I./boards/*/include'
let g:ale_asm_gcc_options = '-x assembler-with-cpp -Wall -m4-nofpu -mb -Wa,--dsp -I./vxgos/bootloader/boards/fxcg50/'
let g:ale_c_gcc_options = '-std=c11 -Wall -D_GNU_SOURCE -D_DEFAULT_SOURCE -I./kernel/include -I./boards/*/include'
let g:ale_cpp_gcc_options = '-std=c11 -Wall -I./kernel/include -I./boards/*/include'
" Enable completion
let g:ale_completion_enabled = 1

0
scripts/bootstrap.sh Normal file
View File

2
scripts/preflight.sh Normal file
View File

@ -0,0 +1,2 @@
#TODO : check vxdev virtual env
#TODO : check if vxSDK is installed

View File

@ -1,15 +1,19 @@
#! /usr/bin/env python3
"""
Vhex kernel developement script
"""
import sys
import cli
from core.logger import log
#---
# Internals
#---
__HELP__ = """ usage: vxdev [ACTION] ...
Actions:
configure perform configuration step
doctor display information about the vxOS and supported feature
build perfrom build step
install perform installation step
uninstall perform uninstallation step
@ -20,17 +24,14 @@ Options:
You can try `vxdev <action> --help` for more information about each action
"""
#---
# Public
#---
def _main(argv):
""" main entry of the script """
if '-h' in argv or '--help' in argv:
print(__HELP__)
log.user(__HELP__)
sys.exit(0)
if not argv:
print(__HELP__)
log.user(__HELP__)
sys.exit(84)
if argv[0] == 'configure':
sys.exit(cli.configure(argv[1:]))
@ -43,4 +44,8 @@ def _main(argv):
print(f"unrecognized argument '{argv[0]}'", file=sys.stderr)
sys.exit(84)
#---
# Public
#---
sys.exit(_main(sys.argv[1:]))

View File

@ -5,9 +5,11 @@ from cli.configure import configure_entry
from cli.build import build_entry
from cli.install import install_entry
from cli.uninstall import uninstall_entry
from cli.doctor import doctor_entry
__all__ = [
'configure',
'doctor',
'build',
'install',
'uninstall',
@ -17,6 +19,10 @@ __all__ = [
# Public
#---
def doctor(argv):
""" invoke doctor script """
return doctor_entry(argv)
def configure(argv):
""" invoke configuration script """
return configure_entry(argv)

View File

@ -4,12 +4,17 @@ Vhex kernel build script
import os
import sys
from core.cmake import cmake_build
from core.logger import log
from core.build import VxOSBuild
__all__ = [
'build_entry'
]
#---
# Internal
#---
__HELP__ = """ usage: vxdev build [OPTIONS] ...
Build script for the Vhex kernel
@ -26,22 +31,22 @@ Options:
def build_entry(argv):
""" Build entry """
if '-h' in argv or '--help' in argv:
print(__HELP__)
log.user(__HELP__)
sys.exit(0)
if 'VXSDK_PKG_TARGET' not in os.environ \
or 'VXSDK_PREFIX_BUILD' not in os.environ \
or 'VXSDK_PREFIX_INSTALL' not in os.environ:
log.error("unable to build the vxGOS without using the vxSDK")
enable_verbose = False
for arg in argv:
if arg in ['-v', '--verbose']:
enable_verbose = True
continue
print(f"unrecognized argument '{arg}'", file=sys.stderr)
sys.exit(84)
log.error(f"unrecognized argument '{arg}'")
if 'VXSDK_PREFIX_BUILD' not in os.environ:
print(
"unable to generate the configuration file, you should use the "
"vxSDK",
file=sys.stderr
)
sys.exit(84)
return cmake_build(os.environ['VXSDK_PREFIX_BUILD'], enable_verbose)
build = VxOSBuild(enable_verbose)
build.configure()
build.build()
return 0

View File

@ -0,0 +1,32 @@
"""
cli.doctor - used to check vxOS feature and process
"""
import sys
from core.logger import log
__all__ = [
'doctor_entry',
]
#----
# Internals
#----
__HELP__ = """ usage: vxdev doctor [OPTIONS] ...
Inspect vxOS source code a fetch information
Options
-h, --help display this help
"""
#---
# Public
#----
def doctor_entry(argv):
""" doctor CLI entry """
if '-h' in argv or '--help' in argv:
log.user(__HELP__)
sys.exit(0)

View File

@ -0,0 +1,108 @@
"""
core.build - build abstraction
"""
import os
import sys
from core.logger import log
from core.build.bootloader import bootloader_configure, bootloader_build
from version import (
# VXGOS_OS_VERSION,
# VXGOS_KERNEL_VERSION,
VXGOS_BOOTLOADER_VERSION,
)
__all__ = [
'VxOSBuild',
]
#---
# Public
#---
class VxOSBuild():
""" performs build abstraction
The build system of vxGOS is particular
TODO
"""
def __new__(cls, *_, **__):
""" try to acquire boards required file and load external post-scripts
"""
obj = super().__new__(cls)
obj._target = os.environ['VXSDK_PKG_TARGET']
obj._prefix_base = f"{os.path.dirname(__file__)}/../../../../vxgos/"
obj._prefix_base = os.path.normpath(obj._prefix_base)
try:
sys.path.append(f"{obj._prefix_base}/boards/{obj._target}/")
mod = __import__(
'generate',
fromlist=[
'generate_kaslr_blob',
'generate_image'
]
)
error_base_str = f"{obj._target} : `generate.py` do not exposes"
if not hasattr(mod, 'generate_kaslr_blob'):
log.emergency(f"{error_base_str} : `generate_kaslr_blob()`")
if not hasattr(mod, 'generate_image'):
log.emergency(f"{error_base_str} : `generate_image()`")
obj._postscript = {
'kalsr' : mod.generate_kaslr_blob,
'image' : mod.generate_image
}
sys.path.pop()
except ImportError as _:
log.error(f"unable to aquire '{obj._target}' post-script")
return obj
def __init__(self, verbose=False):
""" create build abstraction object """
self._verbose = verbose
self._prefix = {
'build' : os.environ['VXSDK_PREFIX_BUILD'],
'bootloader' : f"{self._prefix_base}/bootloader",
'kernel' : f"{self._prefix_base}/kernel",
}
#---
# Attributes
#---
@property
def prefix(self):
""" return the prefix dictionary """
return self._prefix
@property
def target(self):
""" return the selected target """
return self._target
@property
def verbose(self):
""" return if the verbose mode is selected """
return self._verbose
@property
def postscript(self):
""" return if the verbose mode is selected """
return self._postscript
#---
# Public
#---
def configure(self):
""" compile OS/kernel, bootloader and perform post-build scripts """
bootloader_configure(self, VXGOS_BOOTLOADER_VERSION)
def build(self):
""" compile OS/kernel, bootloader and perform post-build scripts """
bootloader_path = bootloader_build(self)
return self.postscript['image'](
self.prefix['build'],
bootloader_path,
'',
)

View File

@ -0,0 +1,80 @@
"""
core.build.bootloader - bootloader build abstraction
"""
import os
import sys
import glob
from core.logger import log
from core.kaslr import kaslr_generate
from core.cmake import (
cmake_generate_template,
cmake_configure,
cmake_build,
)
__all__ = [
'bootloader_configure',
'bootloader_build'
]
#---
# Public
#---
def bootloader_configure(build, version):
""" Configure the bootloader
The configuration step is special. It will first fetch all common source
files (stored at <vxgos/bootloader/src/>) then, fetch board-specific files
like extra source file and the linker script.
After theses operation, a CMake file will be generated in the build prefix
(<env['VXSDK_BUILD_PREFIX']/bootloader/>)
"""
prefix = build.prefix['bootloader']
log.debug('fetching bootloader files...')
log.debug(f"> prefix = {prefix}")
common_src = f"{prefix}/src/**/*.[csS]"
common_src = glob.glob(common_src, recursive=True)
log.debug(f"> common src = {common_src}")
board_src = f"{prefix}/boards/{build.target}/**/*.[csS]"
board_src = glob.glob(board_src, recursive=True)
log.debug(f"> board src = {board_src}")
prefix = f"{build.prefix['build']}/bootloader/"
include_list = [
f"{build.prefix['bootloader']}/include/",
f"{build.prefix['bootloader']}/boards/{build.target}/include/"
]
proj = {
'name' : 'bootloader',
'version' : version,
'linker' : f"{build.prefix['bootloader']}/bootloader.ld",
'ldflags' : '\n '.join(os.environ['VXSDK_BUILD_LDFLAGS'].split(';')),
'cflags' : '\n '.join(os.environ['VXSDK_BUILD_CFLAGS'].split(';')),
'include' : '\n '.join(include_list),
'src' : '\n '.join(common_src + board_src)
}
cmake_generate_template(prefix, proj)
cmake_configure(prefix)
def bootloader_build(build):
""" build the bootloader
@return
> final bootloader blob (complet) path
"""
log.debug('bootloader compilation...')
binpath = f"{build.prefix['build']}/bootloader/bootloader"
log.debug('> cmake build...')
cmake_build(f"{build.prefix['build']}/bootloader/", build.verbose)
log.debug('> generate KSALR table information...')
symtab = kaslr_generate(binpath)
log.debug('> generate architecture-specific blobs...')
return build.postscript['kalsr'](binpath, symtab)

View File

@ -4,26 +4,89 @@ CMake abstraction
import os
import sys
import subprocess
import re
__all__ = [
'cmake_generate_template',
'cmake_configure',
'cmake_build',
'cmake_install',
'cmake_uninstall',
]
#---
# Internals
#---
_CMAKE_TEMPLATE = """
cmake_minimum_required(VERSION 3.15)
#---
# project-specific information
#---
project({VXDEV_PROJ_NAME} VERSION {VXDEV_PROJ_VERSION} LANGUAGES C ASM)
include_directories({VXDEV_BUILD_INCLUDES})
add_compile_options({VXDEV_BUILD_CFLAGS})
add_link_options({VXDEV_BUILD_LDFLAGS})
set(
CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -T {VXDEV_BUILD_LINKER}"
)
#---
# source files listing
#---
set(VXDEV_PROJ_SOURCES
{VXDEV_SOURCE_FILES}
)
#---
# commit projet
#---
add_executable({VXDEV_PROJ_NAME} ${VXDEV_PROJ_SOURCES})
""".strip()
#---
# Pulbic
#---
def cmake_configure(prefix_build, prefix_src):
def cmake_generate_template(prefix_build, proj):
""" Generate a common CMake file """
if os.path.exists(f"{prefix_build}/CMakeLists.txt"):
return
cmakefile = _CMAKE_TEMPLATE
cmakefile = re.sub('{VXDEV_PROJ_NAME}', proj['name'], cmakefile)
cmakefile = re.sub('{VXDEV_PROJ_VERSION}', proj['version'], cmakefile)
cmakefile = re.sub('{VXDEV_BUILD_CFLAGS}', proj['cflags'], cmakefile)
cmakefile = re.sub('{VXDEV_BUILD_LDFLAGS}', proj['ldflags'], cmakefile)
cmakefile = re.sub('{VXDEV_BUILD_LINKER}', proj['linker'], cmakefile)
cmakefile = re.sub('{VXDEV_SOURCE_FILES}', proj['src'], cmakefile)
cmakefile = re.sub('{VXDEV_BUILD_INCLUDES}', proj['include'], cmakefile)
if not os.path.exists(prefix_build):
os.makedirs(prefix_build)
with open(f"{prefix_build}/CMakeLists.txt", 'x', encoding='ascii') as cmk:
cmk.write(cmakefile)
def cmake_configure(prefix_build):
""" Abstract cmake configuration """
toolchain_flag = ''
if toolchain_path := os.environ.get('VXSDK_HOOK_CMAKE_TOOLCHAIN'):
toolchain_flag = f"-DCMAKE_TOOLCHAIN_FILE={toolchain_path}"
shell_cmd = f"cmake {toolchain_flag} -B {prefix_build} -S {prefix_src}"
shell_cmd = f"cmake {toolchain_flag} -B {prefix_build} -S {prefix_build}"
return subprocess.run(shell_cmd.split(), check=False).returncode
#def cmake_configure(prefix_build, prefix_src):
# """ Abstract cmake configuration """
# toolchain_flag = ''
# if toolchain_path := os.environ.get('VXSDK_HOOK_CMAKE_TOOLCHAIN'):
# toolchain_flag = f"-DCMAKE_TOOLCHAIN_FILE={toolchain_path}"
# shell_cmd = f"cmake {toolchain_flag} -B {prefix_build} -S {prefix_src}"
# return subprocess.run(shell_cmd.split(), check=False).returncode
def cmake_build(prefix_build, verbose):
""" Abstract cmake configuration """
shell_cmd = f"cmake --build {prefix_build}"

View File

@ -0,0 +1,33 @@
"""
core.kaslr - generate KASLR symbols table
"""
import subprocess
from core.logger import log
__all__ = [
'kaslr_generate'
]
#---
# Public
#---
def kaslr_generate(binpath):
""" generate KASLR symbols table """
ret = subprocess.run(
f"readelf -r {binpath}".split(),
capture_output=True,
check=False
)
if ret.returncode != 0:
log.error(ret.stderr.decode('utf8'))
symbol_table = []
for i, line in enumerate(ret.stdout.decode('utf8').splitlines()):
if i < 3:
continue
if len(sinfo := line.split()) != 7:
continue
log.debug(f"[{i}] {sinfo}")
symbol_table.append(sinfo)
return symbol_table

View File

@ -0,0 +1,127 @@
"""
core.log - logger object
"""
import sys
__all__ = [
'log'
]
#---
# Internals
#---
_LOG_DEBUG = 7
_LOG_INFO = 6
_LOG_NOTICE = 5
_LOG_USER = 4
_LOG_WARN = 3
_LOG_ERR = 2
_LOG_CRIT = 1
_LOG_EMERG = 0
class _VxLogger():
def __init__(self, logfile=None):
self._logfile = logfile
self._level = _LOG_USER
self._indent = 0
#---
# Internals
#---
def _print(self, level, text, skip_indent, fileno):
if self._level < level:
return 0
if not skip_indent and self._level == _LOG_DEBUG and self._indent > 0:
text = ('>>> ' * self._indent) + text
print(text, file=fileno, end='', flush=True)
return len(text) + 1
#---
# Public properties
#---
@property
def level(self):
""" <property> handle print level """
return self._level
@level.setter
def level(self, level):
""" <property> handle print level """
if level < _LOG_EMERG or level > _LOG_DEBUG:
print(f"[log] level update to {level} is not possible, ignored")
return
self._level = level
@property
def indent(self):
""" <property> handle indentation level for _LOG_DEBUG """
return self._indent
@indent.setter
def indent(self, indent):
""" <property> handle indentation level for _LOG_DEBUG """
if indent < 0:
print(f"[log] indent update to {indent} is not possible, ignored")
return
self._indent = indent
#---
# Public methods
#---
def debug(self, text, end='\n', skip_indent=False):
""" print debug log """
return self._print(
_LOG_DEBUG, f"[DEBUG] {text}{end}", skip_indent, sys.stdout
)
def info(self, text, end='\n', skip_indent=False):
""" print info log """
return self._print(
_LOG_INFO, f"[INFO] {text}{end}", skip_indent, sys.stdout
)
def notice(self, text, end='\n', skip_indent=False):
""" print notice log """
return self._print(
_LOG_NOTICE, f"[NOTICE] {text}{end}", skip_indent, sys.stdout
)
def user(self, text, end='\n', skip_indent=False):
""" print user log """
return self._print(_LOG_USER, f"{text}{end}", skip_indent, sys.stdout)
def warn(self, text, end='\n', skip_indent=False):
""" print warning log """
return self._print(
_LOG_WARN, f"[WARN] {text}{end}", skip_indent, sys.stderr
)
def error(self, text, end='\n', skip_indent=False):
""" print error log """
return self._print(
_LOG_ERR, f"[ERROR] {text}{end}", skip_indent, sys.stderr
)
def critical(self, text, end='\n', skip_indent=False):
""" print critical log """
return self._print(
_LOG_CRIT, f"[CRITICAL] {text}{end}", skip_indent, sys.stderr
)
def emergency(self, text, end='\n', skip_indent=False):
""" print emergency log """
self._print(
_LOG_EMERG, f"[EMERGENCY] {text}{end}", skip_indent, sys.stderr
)
sys.exit(84)
#---
# Public functions
#---
log = _VxLogger()

17
scripts/vxdev/version.py Normal file
View File

@ -0,0 +1,17 @@
"""
version - centralize all version information
"""
__all__ = [
'VXGOS_OS_VERSION',
'VXGOS_KERNEL_VERSION',
'VXGOS_BOOTLOADER_VERSION',
]
#---
# Public
#---
VXGOS_OS_VERSION = '0.0.0'
VXGOS_KERNEL_VERSION = '0.7.0'
VXGOS_BOOTLOADER_VERSION = '0.0.1'

3
vxdev
View File

@ -1,3 +0,0 @@
#!/usr/bin/env bash
/usr/bin/env python3 scripts/vxdev $@

View File

@ -0,0 +1,121 @@
"""
g3a_generator - Casio G3A file generator
"""
import os
import sys
#---
# Internals
#---
def _u32(ptr, idx, data):
""" unsigned 32bits wrapper """
ptr[idx + 0] = (data & 0xff000000) >> 24
ptr[idx + 1] = (data & 0x00ff0000) >> 16
ptr[idx + 2] = (data & 0x0000ff00) >> 8
ptr[idx + 3] = (data & 0x000000ff) >> 0
def _u16(ptr, idx, data):
""" unsigned 16bits injection """
ptr[idx + 0] = (data & 0xff00) >> 8
ptr[idx + 1] = (data & 0x00ff) >> 0
def _u08(ptr, idx, data):
""" unsigned 8bits injection """
ptr[idx + 0] = (data & 0xff) >> 0
def _str(ptr, idx, data):
""" string copy """
for i, j in enumerate(data):
_u08(ptr, idx + i, ord(j))
def _generate_standar_header(addin):
""" generate Casio file standard header """
_str(addin, 0x0000, "USBPower") # watermark
_u08(addin, 0x0008, 0x2c) # addin file type
_u08(addin, 0x0009, 0x00) # watermark
_u08(addin, 0x000a, 0x01) # watermark
_u08(addin, 0x000b, 0x00) # watermark
_u08(addin, 0x000c, 0x01) # watermark
_u08(addin, 0x000d, 0x00) # watermark
_u08(addin, 0x000e, 0x00) # LSB of file size + 0x41 (post)
_u08(addin, 0x000f, 0x01) # watermark
_u32(addin, 0x0010, 0x00000000) # file size (MSB) (post)
_u08(addin, 0x0014, 0x00) # LSB of file size + 0xb8 (post)
_u16(addin, 0x0016, 0x0000) # sum of 8 words starting at 0x7100
def _generate_addin_subheader(addin):
""" generate the g3a addin subheader """
_u32(addin, 0x0020, 0x00000000) # checksum (post)
_u16(addin, 0x0024, 0x0101) # watermark
_u32(addin, 0x002e, 0x00000000) # filesize - 0x7000 - 4 (post)
_str(addin, 0x0040, "TEST") # title padded with zeros
_u32(addin, 0x005c, 0x00000000) # file size (post)
_str(addin, 0x0060, "@TEST") # internal name zero padded
for i in range(0, 8):
_str(addin, 0x006b + (i * 24), "TestMek")
_u08(addin, 0x012b, 0x00) # not usable by EAct
_u32(addin, 0x012c, 0x00000000) # watermark
_str(addin, 0x0130, "01.00.0000") # addin version
_str(addin, 0x013c, "2012.0903.1652") # addin date
_str(addin, 0x0ebc, "Test.g3a") # filename
def _generate_checksums(addin):
""" generate all checksums requested for the g3a format """
filesize = len(addin)
# standard header checksum
_u08(addin, 0x010, (filesize & 0xff000000) >> 24)
_u08(addin, 0x011, (filesize & 0x00ff0000) >> 16)
_u08(addin, 0x012, (filesize & 0x0000ff00) >> 8)
_u08(addin, 0x013, (filesize & 0x000000ff) >> 0)
_u08(addin, 0x00e, addin[0x013] + 0x41)
_u08(addin, 0x014, addin[0x013] + 0xb8)
checksum = 0x00000
for i in range(0, 8):
checksum += int(
addin[0x7100 + (i * 2) : 0x7102 + (i * 2)].hex(),
base=16
)
_u16(addin, 0x016, checksum)
# addin-specific header checksum
_u32(addin, 0x02e, filesize - 0x7000 - 4)
_u32(addin, 0x05c, filesize)
checksum = 0x00000000
for i in range(0x0000, 0x0020):
checksum += addin[i]
for i in range(0x0024, filesize - 4):
checksum += addin[i]
_u32(addin, 0x020, checksum)
_u32(addin, -4, checksum)
def _main(argv):
""" main entry of the project """
if len(argv) != 2:
print('missing <rawbin> or <output>')
sys.exit(84)
rawbin = b''
with open(argv[0], 'rb') as rawfile:
rawbin = rawfile.read()
addin = bytearray(0x7000)
_generate_standar_header(addin)
_generate_addin_subheader(addin)
addin += rawbin
addin += bytearray(4)
_generate_checksums(addin)
if os.path.exists(argv[1]):
os.remove(argv[1])
with open(argv[1], 'xb') as addinfile:
addinfile.write(addin)
return 0
#---
# Public
#---
if __name__ == '__main__':
sys.exit(_main(sys.argv[1:]))

View File

@ -0,0 +1,100 @@
"""
generate - post-build script used to generate bootlaoder blob with KASLR
"""
import os
import subprocess
__all__ = [
'generate_kaslr_blob',
'generate_image'
]
#---
# Public
#---
def generate_kaslr_blob(binpath, symtab):
""" generate bootloader final blob with KASLR
The objectif of this script is to generate the final bootloader blob with
KASLR. To performs this, we will performs 3 steps:
* generate the raw binary file of the bootloader (objcpy)
* generate the raw KALSR symbols table
* generate the complet blootloader image
@args
> binpath (str) - binary file
> symtab (list) - list of all reloc information (readelf -r binpath)
@return
> Nothings
"""
print('- generate raw binary...')
if os.path.exists(f"{binpath}.raw"):
os.remove(f"{binpath}.raw")
subprocess.run(
f"sh-elf-vhex-objcopy -O binary -R .bss {binpath} {binpath}.raw".split(),
capture_output=False,
check=False
)
print('- generate raw symtab...')
if os.path.exists(f"{binpath}.symtab"):
os.remove(f"{binpath}.symtab")
with open(f"{binpath}.symtab", 'xb') as symfile:
for i, sym in enumerate(symtab):
if sym[2] != 'R_SH_DIR32':
continue
print(f" > [{i}] reloc 'R_SH_DIR32' sym at {sym[0]}")
symfile.write(int(sym[0], base=16).to_bytes(4, 'big'))
symfile.write(int('00000000', base=16).to_bytes(4, 'big'))
print('- generate the full bootloader...')
if os.path.exists(f"{binpath}.bzImage"):
os.remove(f"{binpath}.bzImage")
with open(f"{binpath}.bzImage", 'xb') as bzimgfile:
with open(f"{binpath}.raw", 'rb') as rawbinfile:
bzimgfile.write(rawbinfile.read())
with open(f"{binpath}.symtab", 'rb') as symtabfile:
bzimgfile.write(symtabfile.read())
return f"{binpath}.bzImage"
def generate_image(prefix_build, bootloader_path, _):
""" generate complet image (g3a) file
TODO
"""
image = bytearray(0)
with open(bootloader_path, 'rb') as bootloaderfile:
image += bootloaderfile.read()
# (todo) os/kernel
image_size = len(image)
# patch first two instruction of the image (see <vxgos/bootloader>)
image[0] = 0b00000000 # (MSB) nop
image[1] = 0b00001001 # (LSB) nop
image[2] = 0b11010000 # (MSB) mov.l @(1*, PC), r0
image[3] = 0b00000001 # (LSB) mov.l @(1*, PC), r0
image[8] = (image_size & 0xff000000) >> 24
image[9] = (image_size & 0x00ff0000) >> 16
image[10] = (image_size & 0x0000ff00) >> 8
image[11] = (image_size & 0x000000ff) >> 0
bzimage = f"{prefix_build}/vxgos.bzImage"
if os.path.exists(bzimage):
os.remove(bzimage)
with open(bzimage, 'xb') as bzimage:
bzimage.write(image)
prefix = os.path.dirname(__file__)
subprocess.run(
[
'/usr/bin/env',
'python3',
f"{prefix}/g3a_generator.py",
f"{prefix_build}/vxgos.bzImage",
f"{prefix_build}/vxgos.g3a",
],
capture_output=False,
check=False
)

View File

@ -0,0 +1,60 @@
#include "fxcg50_asm_utils.h"
.text
!--
! Public
!--
/* _bios_dclear() (Bdisp_Fill_VRAM) : clear VRAM */
function(_bios_dclear):
mov #3, r5
syscall(0x0275)
/* _bios_udpate() (Bdisp_PutDisp_DD) : display VRAM to screen using DMA */
function(_bios_dupdate):
syscall(0x025f)
/* _bios_dtext() (Bdisp_PrintMiniMini): display string */
function(_bios_dtext):
! prologue
sts.l pr, @-r15
mov.l r8, @-r15
! prepare space for the syscall prototype
add #-4, r15
mov r15, r0 ! *x
add #-4, r15
mov r15, r1 ! *y
add #-4, r15
mov r15, r2 ! color
add #-4, r15
mov r15, r3 ! mode2
! setup info
mov.l r4, @r0 ! *x
mov.l r5, @r1 ! *y
mov.l r6, @r2 ! color
mov #0, r8 ! mode1
mov.l r8, @r3 ! mode2
! invoke syscall
mov r0, r4 ! *x
mov r1, r5 ! *y
mov r7, r6 ! text
mov r8, r7 ! mode1
mov.l 1f, r2 ! syscall trampoline code
mov.w 2f, r0 ! syscall ID
jsr @r0
nop
! restore stack and exit (epilogue)
add #16, r15
mov.l @r15+, r8
lds.l @r15+, pr
rts
nop
.balign 4
1: .long 0x80010070
2: .word 0x021b

View File

@ -0,0 +1,27 @@
#ifndef FXCG50_BOOTLOADER_ASM_UTILS_H
#define FXCG50_BOOTLOADER_ASM_UTILS_H 1
/* function() : define a function using special indication
*
* - the function's name must start with a '_'
* - the function should start in a 4-aligned address in order to benefit from
* the ILP (Instruction Level Parallelism) */
#define function(name) \
.balign 4 ;\
.global _ ## name ;\
_ ## name
/* syscall() : helper used to quickly define Casio syscall invokation
*
* - the syscall trampoline code is common for all syscall
* - r0 contain the syscall ID */
#define syscall(id) \
mov.l 1f, r2 ;\
mov.l 2f, r0 ;\
jmp @r2 ;\
nop ;\
.balign 4 ;\
1: .long 0x80010070 ;\
2: .long id
#endif /* FXCG50_BOOTLOADER_ASM_UTILS_H */

View File

@ -0,0 +1,297 @@
#include "fxcg50_asm_utils.h"
.section .bootloader.pre_text, "ax"
/* ___fxcg50_initialize() : bootstrap routine
We are currently virtualized as a common program (addin) at 0x00300000, but the
code currently executed is physically fragmented through the ROM. So, we cannot
perform KASLR self-relocation here.
The first thing we need to do is find the "user" RAM allocated by Casio (by
analyzing the TLB) and then performs a "self-translation" into this place.
After that, we will be able to perform KASLR self-relocation and classic
"kernel" bootstrapping.. */
function(__fxcg50_primary_bootloader):
! ---
! *CRITICAL*
!
! The next two instructions will be patched *DURING COMPILE TIME* with the
! complete image size (bootloader, KASLR, kernel, OS,...) as follows:
! > mov.l complet_image_size, r0
! > nop
! If not patched, the code will just return and indicate to Casio not to
! restart the application.
! ---
rts
mov #1, r0
bra translation_entry
nop
.long 0x00000000
translation_entry:
! ---
! prepare alias
!
! We don't need to setup the stack because we are loaded from CasioABS
! (Casio OS) as a simple program (addin) with a given stack, which we will
! not use even if we use "special" registers such as r8, r9, ... as we will
! be careful to never return from here.
!
! @note
! - r0 is reused
! - r1 is reused
! - r2 is reused
! - r7 is reused
! - r14 is reused
! ---
#define counter r0
#define utlb_addr_val r1
#define utlb_data_val r2
#define uram_phys_addr r3
#define rom_base_addr r4
#define rom_image_size r5
#define utlb_data_ptr r6
#define utlb_addr_ptr r7
#define uram_virt_ptr r8
#define uram_phys_size r9
#define utlb_valid_bit r10
#define utlb_data_ptr_saved r11
#define utlb_magic_array r12
#define utlb_addr_ptr_saved r13
#define utlb_data_ppn_mask r14
! ---
! Save critical information
!
! Calculate the runtime loaded address of the addin and save the
! compile-time image size information
! ---
! save image size
mov r0, rom_image_size
! precalculate runtime loaded address
mova utlb_fetch_uram_info, r0
add #(___fxcg50_primary_bootloader - utlb_fetch_uram_info), r0
mov r0, rom_base_addr
! ---
! Configure cache
!
! This is a critical part because we will move to the URAM and perform
! self-patching symbols during our execution. So, we need to have a
! consistent cache behavior to help the MPU follow our instructions.
!
! The SH7305 has a special register for that called Cache Control Register
! (CCR), and it seems tobe the same as SH7724 (except that no official POC
! or information can invalidate the instruction Cache (IC) without causing
! the machine to crash):
!
! - Indicate that P1 area use the "Write-back" method
! - Indicate that P0/P3 areas use the "Write-through" method
! - Enable Operand Cache (OC)
! - Enable Instruction Cache (IC)
! - Invalidate all Operand Cache (OC) entries
! ---
mov.l ccr_register_addr, r0
mov.l ccr_register_data, r1
mov.l r1, @r0
synco
! ---
! UTLB scan to find URAM information
!
! As explained above, we first need to find the user RAM given by Casio for
! our application, as we know that is an unused and stable space for us.
!
! We will scan each TLB entry to find the user's RAM. The memory is
! virtually mapped by Casio using the same historical virtual address:
! 0x08100000. Also, all the given RAM is entierly (?) mapped by the
! operating system. Thus, we walk through the TLB until we don't find the
! next memory fragment; this will allow us to find the size of the RAM
! (which varies between models and emulators).
!
! We will also take advantage of the fact that Casio *MAP* the virtual
! address 0x00000000 (NULL) for no valid reason. So, if we find this
! mapping, we will invalidate it to be sure that a NULL manipulated pointer
! will cause a TLBmiss exception.
!
! TODO : invalidate NULL page
! ---
utlb_fetch_uram_info:
! fetch external information
mov.l data_00000100, utlb_valid_bit
mov.l data_08100000, uram_virt_ptr
mov.l data_f6000000, utlb_addr_ptr
mov.l data_14100c0a, utlb_magic_array
mov.l data_1ffffc00, utlb_data_ppn_mask
mov.l data_f7000000, utlb_data_ptr
! prepare internal vars
mov #0, counter
mov #-1, uram_phys_addr
mov #0, uram_phys_size
mov utlb_data_ptr, utlb_data_ptr_saved
mov utlb_addr_ptr, utlb_addr_ptr_saved
utlb_walk_loop:
! validity check
! @note
! - check the validity bit for each UTLB data and address entry
! - both data and address entry have the same Valid bit position
mov.l @utlb_addr_ptr, utlb_addr_val
tst utlb_valid_bit, utlb_addr_val
bt utlb_walk_cond_check
mov.l @utlb_data_ptr, utlb_data_val
tst utlb_valid_bit, utlb_data_val
bt.s utlb_walk_cond_check
! check VPN validity
! @note
! - "UTLB Address Array" (p239) - Figure 7.14
! - we need to clear the first 10 bits of the fetched UTLB data to get the
! the "real" virtual address (eliminate ASID, Dirty and Valid bits)
shlr8 utlb_addr_val
shlr2 utlb_addr_val
shll8 utlb_addr_val
shll2 utlb_addr_val
cmp/eq uram_virt_ptr, utlb_addr_val
bf.s utlb_walk_cond_check
! fetch the page size
! @note
! - "Unified TLB (UTLB) Configuration"(p221)
! - page size is weird to calculate for many hardware reasons, and this code
! is the literal translation of :
! > size = ((data->SZ1 << 1) | data->SZ2) << 3;
! > size = 1 << ((0x14100c0a >> size) & 0xff);
mov #-1, r1
mov utlb_data_val, r0
tst #128, r0
mov #-1, r7
negc r1, r1
tst #16, r0
add r1, r1
negc r7, r7
or r7, r1
mov utlb_magic_array,r7
shll2 r1
add r1, r1
neg r1, r1
shad r1, r7
extu.b r7, r1
mov #1, r7
shld r1, r7
! update counter / information
add r7, uram_phys_size
add r7, uram_virt_ptr
! check if the URAM physical address is already set
mov uram_phys_addr,r0
cmp/eq #-1,r0
bf utlb_page_found_restart
! calculate the physical address of the page (URAM)
! @note
! - "UTLB Data Array"(p240) - Figure 7.15
! - to fetch the Physical Address, we just need to isolate the PPN
and utlb_data_ppn_mask, utlb_data_val
shlr8 utlb_data_val
shlr2 utlb_data_val
mov utlb_data_val, uram_phys_addr
shll8 uram_phys_addr
shll2 uram_phys_addr
utlb_page_found_restart:
mov r13, utlb_addr_ptr
mov r11, utlb_data_ptr
bra utlb_walk_loop
mov #0, counter
utlb_walk_cond_check:
! update internal counter
! @notes
! - only 64 UTLB entry
! - UTLB entry (for data and address) gap is 0x100
mov.l data_00000100, r1
add #1, counter
cmp/eq #64, counter
add r1, utlb_addr_ptr
bf.s utlb_walk_loop
add r1, utlb_data_ptr
! ---
! Self-translation to URAM
!
! Now that we have the user RAM entry address (uram_phys_addr) and its size
! (uram_phys_size), we can self-translate to this location using a dummy
! byte-per-byte copy.
!
! Note that, for now, no random installation offset is performed since
! predicting uncertain behavior is complex to choose for now.
! ---
self_translation:
! fetch bootloader ROM geometry information
mov rom_base_addr, r0
mov rom_image_size, r2
! generate uncachable P2 URAM address
! TODO
! - random offset
! - check oversize
mov.l data_a0000000, r1
or uram_phys_addr, r1
! dump the complet image into URAM
self_dump_uram:
mov.b @r0, r14
mov.b r14, @r1
dt r2
add #1, r0
add #1, r1
bf.s self_dump_uram
nop
! Prepare the self-translation by calculating the new PC position using a
! P1 address to allow caching to be performed.
! @note
! - ___fxcg50_bootloader_start is a global symbol compiled with NULL as the
! base address. So, we don't have to calculate the gap between the start
! of the ROM and the symbol.
mov.l data_80000000, r1
or uram_phys_addr, r1
mov.l real_bootloader_start, r0
add r1, r0
! self-translation
mov rom_image_size, r6
mov uram_phys_addr, r5
mov rom_base_addr, r4
jmp @r0
nop
.balign 4
data_08100000: .long 0x08100000 ! Casio addin load virtual address
data_f6000000: .long 0xf6000000 ! SH7305 UTLB Address address
data_f7000000: .long 0xf7000000 ! SH7305 UTLB Data addresss
data_14100c0a: .long 0x14100c0a ! Magic UTLB page size table
data_1ffffc00: .long 0x1ffffc00 ! UTLB Address PPN mask
data_00000100: .long 0x00000100 ! UTLB entry gap and UTLB validity bit
data_a0000000: .long 0xa0000000 ! P2 base address
data_80000000: .long 0x80000000 ! P1 base address
ccr_register_addr: .long 0xff00001c ! SH7305.CACHE.CCR register address
ccr_register_data: .long 0x0000010f ! CCR configuration
real_bootloader_start:
.long ___fxcg50_bootloader_start

View File

@ -0,0 +1,127 @@
#include "fxcg50_asm_utils.h"
.section .bootloader.text, "ax"
/* ___fxcg50_bootloader_start() : real bootstrap entry
Now we are in the URAM we can performs KASLR patching, setup stack and involve
the first high-level C code which will perform kernel setup.
The primary (fake) bootloader (previous operations) have setup some arg:
- r4 = ROM relocation base address
- r5 = RAM relocation base address (physical)
- r6 = image size */
function(__fxcg50_bootloader_start):
! ---
! prepare alias
! ---
#define rom_reloc_base r4
#define ram_reloc_base r5
#define image_size r6
! ---
! KASLR application
!
! perform KASLR patch by using the symbols table information injected
! during bootloader build steps at the end of the bootloader code marked
! with ___bootloader_code_end.
!
! The content of the table has been generated during post-compiling script
! ---
! The table symbol is not aready resolved (its our job), we must manually
! calculate the real address of the symbols table
mov.l bootloader_code_end, r0
mov.l p1_addr_base, r1
mov.l p2_addr_base, r2
or ram_reloc_base, r2
or ram_reloc_base, r1
add r2, r0
! walk trough the symbols table and patch all location
! @note
! - we MUST perform patching using P2 (uncachable) area to avoid
! inconsistancy behaviour with the cache.
! - symbols are relocalize through P1 (cachable) area
kaslr_symbol_patch_loop:
mov.l @r0, r8
tst r8, r8
bt kaslr_commit
add r2, r8
mov.l @r8, r9
add r1, r9
mov.l r9, @r8
mov.l r8, @r0
add #4, r0
bra kaslr_symbol_patch_loop
nop
kaslr_commit:
! Now that KASLR symbols has been updated using uncachable area (P2), we
! need to invalitate all Operands Cache entry that the MPU have possibly
! setup to avoid inconsistant `mov.x` behaviour
! @note
! - CCR.OCI = 1 -> Operand Cache Invalidation (self-cleared to 0)
mov.l ccr_reg_addr, r1
mov.l @r1, r0
or #0x08, r0
mov.l r0, @r1
synco
setup_stack:
! TODO : watermark stack area for statistics
! TODO : stack switch
! TODO : stack canary
bootloader_c_invokation:
mov.l bootloader_main, r0
jsr @r0
nop
! ---
! bootloader panic
!
! As we have probably wierdly restored hadware information, if the
! bootloader main routine return we simply display black screen. You can
! uncomment following instruction to allows getkey() to return to the menu
! (debug only)
! ---
bootloader_paniktamer:
mov.l syscall_trampoline, r8
mov #0x4a, r4
mov #3, r5
mov.l syscall_id, r0
jsr @r8
nop
test1:
! add #-4, r15
! mov r15, r4 ! column
! add #-4, r15
! mov r15, r5 ! row
! add #-4, r15
! mov r15, r1 ! keycode
! mov #0, r6 ! type of waiting (KEYWAIT_HALTON_TIMEOFF)
! mov #0, r7 ! timeout period
! mov.l r1, @-r15 ! keycode
! mov #0, r2
! mov.l r2, @-r15 ! [menu] key return to menu
! mov.l getkey_id, r0
! jsr @r8
! nop
bra test1
nop
.balign 4
bootloader_main: .long _bootloader_main
bootloader_code_end: .long ___bootloader_code_end
syscall_trampoline: .long 0x80020070
syscall_id: .long 0x0276
p2_addr_base: .long 0xa0000000
p1_addr_base: .long 0x80000000
getkey_id: .long 0x000012bf ! GetKeyWait_OS syscall ID
ccr_reg_addr: .long 0xff00001c ! SH7305.CACHE.CCR register address

View File

@ -0,0 +1,97 @@
PHDRS
{
vxgos_load PT_LOAD FLAGS(1);
}
SECTIONS
{
/* set the base address to zero. This is really usefull when patching
* symbols "on-the-fly" during runtime */
. = 0x00000000 ;
/* used for the self-translation step if needed */
___bootloader_code_start = . ;
/* explicitly isolate the "pre text" code which should be executed first */
.bootloader : {
KEEP(*(.bootloader.pre_text))
KEEP(*(.bootloader.text))
} : vxgos_load
/* assembly code should be inserted here */
.text : {
*(.text.entry)
*(.text)
*(.text.*)
}
/* Read-only sections */
.rodata : {
*(.rodata)
*(.rodata.*)
}
/* uninitialized globals (must be 16-aligned) */
.bss ALIGN(16) (NOLOAD) : {
*(.bss)
*(.bss.*)
. = ALIGN(16) ;
}
/* readable / writable data (must be 16-aligned) */
.data ALIGN(16) : {
*(.data)
*(.data.*)
*(COMMON)
. = ALIGN(16) ;
}
/* We do not want any PLT/GOT information in this part of the code */
.plt : { *(.plt) }
.plt.got : { *(.plt.got) }
ASSERT(SIZEOF(.plt) == 0, "unexpected '.plt' entries")
ASSERT(SIZEOF(.plt.got) == 0, "unexpected '.plt.got' entries")
/* Dynamic linking information
* Generated by linker when linking with "-pie" */
.dynamic : { *(.dynamic) }
.rela.dyn : { *(.rela.dyn) }
.rel.dyn : { *(.rel.dyn) }
.relr.dyn : { *(.relr.dyn) }
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.gnu.hash : { *(.gnu.hash) }
/* Gloabal offset table
* We don't want/need any GOT entries, but BFD LD insert an entry
* ("_DYNAMIC") into '.got', as required by ABI, and three empty entry on
* some architecture. */
.got : { *(.got.plt) *(.got) }
ASSERT(
SIZEOF(.got) <= 4 * 8,
"'.got' should contain only ABI-required entries"
)
/* indicate the end of the bootloader
* this symbols will be used to resolve KASLR patching during runtime */
___bootloader_code_end = . ;
/* unwanted sections */
/DISCARD/ : {
*(.gnu.*)
*(.debug_info)
*(.debug_abbrev)
*(.debug_loc)
*(.debug_aranges)
*(.debug_ranges)
*(.debug_line)
*(.debug_str)
*(.debug_*)
*(.jcr)
*(.eh_frame_hdr)
*(.eh_frame)
*(.comment)
*(.interp)
}
}

View File

@ -0,0 +1,13 @@
#ifndef BOOTLOADER_BIOS_H
#define BOOTLOADER_BIOS_H 1
/* _bios_dclear() : clear VRAM */
extern void _bios_dclear(int color);
/* _bios_dtext() : draw raw text */
extern void _bios_dtext(int x, int y, int color, char *text);
/* _bios_dudpate() : display VRAM to screen */
extern void _bios_dupdate(void);
#endif /* BOOTLOADER_BIOS_H */

View File

@ -0,0 +1,18 @@
#ifndef BOOTLOADER_DISPLAY_H
#define BOOTLOADER_DISPLAY_H 1
#define C_WHITE 0x0000
/* dclear() : clear VRAM with unified color */
extern void dclear(int color);
/*dtext() : display raw text to VRAM */
extern void dtext(int x, int y, int color, char *text);
/* dprint() : printf-like dtext() */
extern void dprint(int x, int y, int color, char *format, ...);
/* dupdate() : update VRAM to screen */
extern void dupdate(void);
#endif /* BOOTLOADER_DISPLAY_H */

View File

@ -0,0 +1,44 @@
#include <stdio.h>
#include <alloca.h>
#include <bootloader/display.h>
#include <bootloader/bios.h>
//---
// Public
//---
/* dclear() : clear VRAM with unified color */
void dclear(int color)
{
_bios_dclear(color);
}
/*dtext() : display raw text to VRAM */
void dtext(int x, int y, int color, char *text)
{
_bios_dtext(x, y, color, text);
}
/* dprint() : printf-like dtext() */
void dprint(int x, int y, int color, char *format, ...)
{
char *buffer;
va_list ap;
buffer = alloca(128);
if (buffer != NULL)
return;
va_start(ap, format);
vsnprintf(buffer, 128, format, ap);
va_end(ap);
_bios_dtext(x, y, color, buffer);
}
/* dupdate() : update VRAM to screen */
void dupdate(void)
{
_bios_dupdate();
}

View File

@ -0,0 +1,46 @@
#include <bootloader/display.h>
#include <stdint.h>
//---
// Public
//---
int bootloader_main(void)
{
uint16_t const config[] = {0x0000, 0xc003, 0xc001, 0xc005};
volatile uint16_t *psela = (void*)0xa405014e;
volatile uint8_t *ddk_cncr = (void*)0xa44c0020;
volatile uint16_t *ddclk0 = (void*)0xa44c0000;
/* conf */
*psela = *psela & 0xfcff; // Stop module.
*psela = *psela | 0x0100; // configure LED pin to ouput
*ddclk0 = 0xffff;
/* DEL loop */
int offset = 1;
int index = 0;
while (1)
{
/* update DEL conf */
*ddk_cncr = *ddk_cncr & 0xfe; // Stop External Clock 0
*ddclk0 = config[index]; // Casio clock config.
*ddk_cncr = *ddk_cncr | 0x01; // Start LED clock.
/* Wait a bit */
for (int i = 0 ; i < 500000 ; i = i + 1) {
__asm__ volatile ("nop");
};
/* Update Configuration part. */
index = index + offset;
if (index == 3)
offset = -1;
if (index == 0)
offset = 1;
}
while (1) {
;
}
}

View File

@ -0,0 +1,6 @@
int bootloader_c_test(void)
{
while (1);
}

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Some files were not shown because too many files have changed in this diff Show More