commit 45543d302ff1360e3835ebc4af75e4566d94fef9 Author: Eugen Wissner Date: Tue Jun 24 22:12:48 2025 +0200 Initial commit diff --git a/common.s b/common.s new file mode 100644 index 0000000..f61321e --- /dev/null +++ b/common.s @@ -0,0 +1,630 @@ +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v. 2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at https://mozilla.org/MPL/2.0/. + +.global _is_alpha, _is_digit, _is_alnum, _is_upper, _is_lower +.global _write_s, _read_file, _write_error, _write_c, _write_i, _print_i +.global _memcmp, _memchr, _memmem, _memcpy, _mmap +.global _current, _get, _advance, _label_counter +.global _divide_by_zero_error, _exit, _strings_index, _string_equal + +.section .rodata + +.equ SYS_READ, 63 +.equ SYS_WRITE, 64 +.equ SYS_EXIT, 93 +.equ SYS_MMAP2, 222 +.equ STDIN, 0 +.equ STDOUT, 1 +.equ STDERR, 2 +.equ PROT_READ, 0x1 +.equ PROT_WRITE, 0x2 +.equ MAP_PRIVATE, 0x02 +.equ MAP_ANONYMOUS, 0x20 + +new_line: .ascii "\n" + +.section .text + +# Write the current token to stderr. Ends the output with a newline. +# +# a0 - String pointer. +# a1 - String length. +.type _write_error, @function +_write_error: + mv t0, a0 + mv t1, a1 + + li a0, STDERR + mv a1, t0 + mv a2, t1 + li a7, SYS_WRITE + ecall + + li a0, STDERR + la a1, new_line + li a2, 1 + li a7, SYS_WRITE + ecall + + ret + +# a0 - First pointer. +# a1 - Second pointer. +# a2 - The length to compare. +# +# Returns 0 in a0 if memory regions are equal. +.type _memcmp, @function +_memcmp: + mv t0, a0 + li a0, 0 + +.Lmemcmp_loop: + beqz a2, .Lmemcmp_end + + lbu t1, (t0) + lbu t2, (a1) + sub a0, t1, t2 + + bnez a0, .Lmemcmp_end + + addi t0, t0, 1 + addi a1, a1, 1 + addi a2, a2, -1 + + j .Lmemcmp_loop + +.Lmemcmp_end: + ret + +# Detects if a0 is an uppercase character. Sets a0 to 1 if so, otherwise to 0. +.type _is_upper, @function +_is_upper: + li t0, 'A' - 1 + sltu t1, t0, a0 # t1 = a0 >= 'A' + + sltiu t2, a0, 'Z' + 1 # t2 = a0 <= 'Z' + and a0, t1, t2 # t1 = a0 >= 'A' & a0 <= 'Z' + + ret + +# Detects if a0 is an lowercase character. Sets a0 to 1 if so, otherwise to 0. +.type _is_lower, @function +_is_lower: + li t0, 'a' - 1 + sltu t2, t0, a0 # t2 = a0 >= 'a' + + sltiu t3, a0, 'z' + 1 # t3 = a0 <= 'z' + and a0, t2, t3 # t2 = a0 >= 'a' & a0 <= 'z' + + ret + +# Detects if the passed character is a 7-bit alpha character or an underscore. +# The character is passed in a0. +# Sets a0 to 1 if the character is an alpha character or underscore, sets it to 0 otherwise. +.type _is_alpha, @function +_is_alpha: + # Prologue. + addi sp, sp, -16 + sw ra, 12(sp) + sw s0, 8(sp) + addi s0, sp, 16 + + sw a0, 4(sp) + + call _is_upper + sw a0, 0(sp) + + lw a0, 4(sp) + call _is_lower + + lw t0, 4(sp) + xori t1, t0, '_' + seqz t1, t1 + + lw t0, 0(sp) + or a0, a0, t0 + or a0, a0, t1 + + # Epilogue. + lw ra, 12(sp) + lw s0, 8(sp) + addi sp, sp, 16 + ret + +# Detects whether the passed character is a digit +# (a value between 0 and 9). +# +# Parameters: +# a0 - Exemined value. +# +# Sets a0 to 1 if it is a digit, to 0 otherwise. +.type _is_digit, @function +_is_digit: + li t0, '0' - 1 + sltu t1, t0, a0 # t1 = a0 >= '0' + + sltiu t2, a0, '9' + 1 # t2 = a0 <= '9' + + and a0, t1, t2 + + ret + +.type _is_alnum, @function +_is_alnum: + # Prologue. + addi sp, sp, -16 + sw ra, 12(sp) + sw s0, 8(sp) + addi s0, sp, 16 + + sw a0, 4(sp) + + call _is_alpha + sw a0, 0(sp) + + lw a0, 4(sp) + call _is_digit + + lw a1, 0(sp) + or a0, a0, a1 + + # Epilogue. + lw ra, 12(sp) + lw s0, 8(sp) + addi sp, sp, 16 + ret + +# Writes a string to the standard output. +# +# Parameters: +# a0 - Length of the string. +# a1 - String pointer. +.type _write_s, @function +_write_s: + # Prologue. + addi sp, sp, -8 + sw ra, 4(sp) + sw s0, 0(sp) + addi s0, sp, 8 + + mv a2, a0 + li a0, STDOUT + li a7, SYS_WRITE + ecall + + # Epilogue. + lw ra, 4(sp) + lw s0, 0(sp) + addi sp, sp, 8 + ret + +# Reads standard input into a buffer. +# a0 - Buffer pointer. +# a1 - Buffer size. +# +# Sets s1 to the buffer passed in a0. +# +# Returns the amount of bytes written in a0. +.type _read_file, @function +_read_file: + # Prologue. + addi sp, sp, -8 + sw ra, 4(sp) + sw s0, 0(sp) + addi s0, sp, 8 + + mv s1, a0 + + li a0, STDIN + mv a2, a1 + mv a1, s1 + li a7, SYS_READ + ecall + + # Epilogue. + lw ra, 4(sp) + lw s0, 0(sp) + addi sp, sp, 8 + ret + +# Terminates the program. a0 contains the return code. +# +# Parameters: +# a0 - Status code. +.type _exit, @function +_exit: + li a7, SYS_EXIT + ecall + # ret + +.type _divide_by_zero_error, @function +_divide_by_zero_error: + addi a7, zero, 172 # getpid + ecall + + addi a1, zero, 8 # SIGFPE + addi a7, zero, 129 # kill + ecall + ret + +# Writes a number to a string buffer. +# +# t0 - Local buffer. +# t1 - Constant 10. +# t2 - Current character. +# t3 - Whether the number is negative. +# +# Parameters: +# a0 - Whole number. +# a1 - Buffer pointer. +# +# Sets a0 to the length of the written number. +.type _print_i, @function +_print_i: + addi sp, sp, -32 + sw ra, 28(sp) + sw s0, 24(sp) + addi s0, sp, 32 + + li t1, 10 + addi t0, s0, -9 + + li t3, 0 + bgez a0, .Lprint_i_digit10 + li t3, 1 + neg a0, a0 + +.Lprint_i_digit10: + rem t2, a0, t1 + addi t2, t2, '0' + sb t2, 0(t0) + div a0, a0, t1 + addi t0, t0, -1 + bne zero, a0, .Lprint_i_digit10 + + beq zero, t3, .Lprint_i_write_call + addi t2, zero, '-' + sb t2, 0(t0) + addi t0, t0, -1 + +.Lprint_i_write_call: + mv a0, a1 + addi a1, t0, 1 + sub a2, s0, t0 + addi a2, a2, -9 + sw a2, 0(sp) + + call _memcpy + + lw a0, 0(sp) + + lw ra, 28(sp) + lw s0, 24(sp) + addi sp, sp, 32 + ret + +# Writes a number to the standard output. +# +# Parameters: +# a0 - Whole number. +.type _write_i, @function +_write_i: + addi sp, sp, -32 + sw ra, 28(sp) + sw s0, 24(sp) + addi s0, sp, 32 + + addi a1, sp, 0 + call _print_i + + addi a1, sp, 0 + call _write_s + + lw ra, 28(sp) + lw s0, 24(sp) + addi sp, sp, 32 + ret + +# Writes a character from a0 into the standard output. +.type _write_c, @function +_write_c: + # Prologue + addi sp, sp, -16 + sw ra, 12(sp) + sw s0, 8(sp) + addi s0, sp, 16 + + sb a0, 4(sp) + li a0, STDOUT + addi a1, sp, 4 + li a2, 1 + li a7, SYS_WRITE + ecall + + # Epilogue. + lw ra, 12(sp) + lw s0, 8(sp) + add sp, sp, 16 + ret + +# a0 - Pointer to an array to get the first element. +# +# Dereferences a pointer and returns what is on the address in a0. +.type _get, @function +_get: + lw a0, (a0) + ret + +# Searches for the occurences of a character in the given memory block. +# +# Parameters: +# a0 - Memory block. +# a1 - Needle. +# a2 - Memory size. +# +# Sets a0 to the pointer to the found character or to null if the character +# doesn't occur in the memory block. +.type _memchr, @function +_memchr: +.Lmemchr_loop: + beqz a2, .Lmemchr_nil # Exit if the length is 0. + + lbu t0, (a0) # Load the character from the memory block. + beq t0, a1, .Lmemchr_end # Exit if the character was found. + + # Otherwise, continue with the next character. + addi a0, a0, 1 + addi a2, a2, -1 + + j .Lmemchr_loop + +.Lmemchr_nil: + li a0, 0 + +.Lmemchr_end: + ret + +# Locates a substring. +# +# Parameters: +# a0 - Haystack. +# a1 - Haystack size. +# a2 - Needle. +# a3 - Needle size. +# +# Sets a0 to the pointer to the beginning of the substring in memory or to 0 +# if the substring doesn't occur in the block. +.type _memmem, @function +_memmem: + # Prologue. + addi sp, sp, -24 + sw ra, 20(sp) + sw s0, 16(sp) + addi s0, sp, 24 + + # Save preserved registers. They are used to keep arguments. + sw s1, 12(sp) + sw s2, 8(sp) + sw s3, 4(sp) + sw s4, 0(sp) + + mv s1, a0 + mv s2, a1 + mv s3, a2 + mv s4, a3 + +.Lmemmem_loop: + blt s2, s3, .Lmemmem_nil # Exit if the needle length is greater than memory. + + mv a0, s1 + mv a1, s3 + mv a2, s4 + call _memcmp + + mv t0, a0 # memcmp result. + mv a0, s1 # Memory pointer for the case the substring was found. + beqz t0, .Lmemmem_end + + addi s1, s1, 1 + add s2, s2, -1 + + j .Lmemmem_loop + +.Lmemmem_nil: + li a0, 0 + +.Lmemmem_end: + + # Restore the preserved registers. + lw s1, 12(sp) + lw s2, 8(sp) + lw s3, 4(sp) + lw s4, 0(sp) + + # Epilogue. + lw ra, 20(sp) + lw s0, 16(sp) + add sp, sp, 24 + ret + +# Copies memory. +# +# Parameters: +# a0 - Destination. +# a1 - Source. +# a2 - Size. +# +# Preserves a0. +.type _memcpy, @function +_memcpy: + mv t0, a0 + +.Lmemcpy_loop: + beqz a2, .Lmemcpy_end + + lbu t1, (a1) + sb t1, (a0) + + addi a0, a0, 1 + addi a1, a1, 1 + addi a2, a2, -1 + + j .Lmemcpy_loop + +.Lmemcpy_end: + mv a0, t0 + ret + +# Searches for a string in a string array. +# +# Parameters: +# a0 - Number of elements in the string array. +# a1 - String array. +# a2 - Needle length. +# a3 - Needle. +# +# Sets a0 to the 1-based index of the needle in the haystack or to 0 if the +# element could not be found. +.type _strings_index, @function +_strings_index: + # Prologue. + addi sp, sp, -32 + sw ra, 28(sp) + sw s0, 24(sp) + addi s0, sp, 32 + + sw s1, 20(sp) + mv s1, a0 + sw s2, 16(sp) + mv s2, a1 + sw s3, 12(sp) + mv s3, a2 + sw s4, 8(sp) + mv s4, a3 + sw s5, 4(sp) + li s5, 0 # Index counter. + +.Lstrings_index_loop: + addi s5, s5, 1 + beqz s1, .Lstrings_index_missing + + lw a2, (s2) # Read the length of the current element in the haystack. + bne a2, s3, .Lstrings_index_next # Lengths don't match, skip the iteration. + + addi a0, s2, 4 + mv a1, s4 + call _memcmp + + beqz a0, .Lstrings_index_end + +.Lstrings_index_next: + # Advance the pointer, reduce the length. + lw a2, (s2) + addi s2, s2, 4 + add s2, s2, a2 + addi s1, s1, -1 + j .Lstrings_index_loop + +.Lstrings_index_missing: + li s5, 0 + +.Lstrings_index_end: + mv a0, s5 + + lw s1, 20(sp) + lw s2, 16(sp) + lw s3, 12(sp) + lw s4, 8(sp) + lw s5, 4(sp) + + # Epilogue. + lw ra, 28(sp) + lw s0, 24(sp) + add sp, sp, 32 + ret + +# Compares two strings for equality. +# +# Parameters: +# a0 - Length of the first string. +# a1 - Pointer to the first string. +# a2 - Length of the second string. +# a3 - Pointer to the second string. +# +# Sets a0 to 1 if the string are equal, to 0 if not. +.type _string_equal, @function +_string_equal: + # Prologue. + addi sp, sp, -32 + sw ra, 28(sp) + sw s0, 24(sp) + addi s0, sp, 32 + + # Compare string lengths. + bne a0, a2, .Lstring_equal_not_found + + # If lengths match, compare the content. + mv a0, a1 + mv a1, a3 + # a2 is already set to the length. + call _memcmp + + bnez a0, .Lstring_equal_not_found + + li a0, 1 + j .Lstring_equal_end + +.Lstring_equal_not_found: + mv a0, zero + +.Lstring_equal_end: + # Epilogue. + lw ra, 28(sp) + lw s0, 24(sp) + addi sp, sp, 32 + ret + +# Sets a0 to the mapping address. +.type _mmap, @function +_mmap: + li a0, 0 # Address at which to create the mapping. + li a1, 4096 # The length of the mapping. + li a2, PROT_READ | PROT_WRITE # Protection flags. + li a3, MAP_ANONYMOUS | MAP_PRIVATE # The mapping is not backed by a file. + li a4, -1 # File descriptor. + li a5, 0 # Page offset. + li a7, SYS_MMAP2 + ecall + + ret + +# Sets the a0 to the current position in the source text (s1). +.type _current, @function +_current: + mv a0, s1 + ret + +# Advances the position of the source text. +# +# Parameters: +# a0 - The number of bytes to advance. +.type _advance, @function +_advance: + add s1, s1, a0 + ret + +# Advances the global label counter by 1 setting a0 to the previous value. +# +# Parameters: +# a0 - If it is 0, resets the counter to 1. +.type _label_counter, @function +_label_counter: + bnez a0, .Llabel_counter_advance + li s2, 0 + +.Llabel_counter_advance: + mv a0, s2 + addi s2, s2, 1 + + ret diff --git a/kernel.ld b/kernel.ld new file mode 100644 index 0000000..4c5d027 --- /dev/null +++ b/kernel.ld @@ -0,0 +1,30 @@ +OUTPUT_ARCH( "riscv" ) +OUTPUT_FORMAT("elf32-littleriscv") +ENTRY(boot) + +SECTIONS { + . = 0x80200000; + + .text :{ + KEEP(*(.text.boot)); + *(.text .text.*); + } + + .rodata : ALIGN(4) { + *(.rodata .rodata.*); + } + + .data : ALIGN(4) { + *(.data .data.*); + } + + .bss : ALIGN(4) { + __bss = .; + *(.bss .bss.* .sbss .sbss.*); + __bss_end = .; + } + + . = ALIGN(4); + . += 128 * 1024; /* 128KB */ + __stack_top = .; +} diff --git a/kernel.s b/kernel.s new file mode 100644 index 0000000..37b086d --- /dev/null +++ b/kernel.s @@ -0,0 +1,73 @@ +.global kernel_main, __bss, __bss_end, __stack_top + +.section .rodata + +hello_world: .asciz "\nHello world!\n" + +.section .text + +bzero: + la t0, __bss + la t1, __bss_end + +.Lbzero_loop: + bgt t0, t1, .Lbzero_end + sw zero, (t0) + addi t0, t0, 4 + +.Lbzero_end: + ret + +# Arguments: +# a0 - Character. +.type putchar, @function +putchar: + li a1, 0 + li a2, 0 + li a3, 0 + li a4, 0 + li a5, 0 + li a6, 0 + li a7, 1 # sbi_console_putchar. + + ecall + ret + +.type kernel_main, @function +kernel_main: + # Prologue. + addi sp, sp, -32 + sw ra, 28(sp) + sw s0, 24(sp) + addi s0, sp, 32 + + sw s1, 20(sp) + + la s1, hello_world + +.Lkernel_main_if: + lw a0, (s1) + beqz a0, .Lkernel_main + + call putchar + addi s1, s1, 1 + + j .Lkernel_main_if + +.Lkernel_main: + j .Lkernel_main + + lw s1, 20(sp) + + # Epilogue. + lw ra, 28(sp) + lw s0, 24(sp) + add sp, sp, 32 + ret + +.section .text.boot +boot: + la a0, __stack_top + mv sp, a0 # Set the stack pointer + call bzero + j kernel_main diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..9406101 --- /dev/null +++ b/run.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +set -xue + +# QEMU file path +QEMU=qemu-system-riscv32 + +# Path to clang and compiler flags +CC=../riscv32-ilp32d--glibc/bin/riscv32-linux-gcc +CFLAGS="-O2 -g3 -Wall -march=rv32im -mabi=ilp32 -fno-stack-protector -ffreestanding -nostdlib -static" + +# Build the kernel +$CC $CFLAGS -T kernel.ld -Wl,-Map=kernel.map -o kernel.elf kernel.s common.s + +# Start QEMU +$QEMU -machine virt -bios default -nographic -serial mon:stdio --no-reboot -kernel kernel.elf