stardance has been extended another month! the new deadline is october 31 :)

You are browsing as a guest. Sign up (or log in) to start making projects!

MarioS271

@MarioS271

Joined June 8th, 2026

  • 70Devlogs
  • 6Projects
  • 5Ships
  • 67Votes
Hi!👋 
I'm MarioS271, a 16 year old from Austria who's studying electrical engineering. I also have a passion for especially low-level and embedded programming.

👉 https://www.marios271.net
Open comments for this post

2h 16m 40s logged

whoops

Turns out, when testing the new debug write syscall, it was slightly broken when passing wrong params, specifially when passing an unmapped or invalid address as a pointer to the string to write. Because of that, a page fault was triggered in the syscall, which blew up the stack, which was for some reason already filled more than halfway (around 9 KiB)?!?

Side note: in a syscall handler, there are only 16 KiB of stack memory available, and therefore 9 KiB before any error handling is already kind of a lot (normally it should be less than half in this application)


What was actually wrong

Turns out, the error was the freaking kprint stuff. Yes, the funny kdebug!() and knotice!() logging macros.. The main issue is that they’re macros, which expand in place.

Why that’s a problem

In Syscall::dispatch, there’s two kdebug!() calls. That blows the method’s needed stack to almost 5 KiB for a simple dispatcher function with 10 lines, which is unacceptable. The problem is that the stack memory for all the kdebug!() writes gets reserved as soon as the method is called, and each kprint macro call has a 2 KiB buffer and more behind it that it needs.

The Solution

To fix this, I replaced the internal __kprint macro with a function. This means that now the needed stack for the logging action is only reserved during the logging, not during the whole parent function.
By doing this, I managed to cut the debug write system call’s memory footprint from around 8 KiB to just 1.5 KiB, which is a huge improvement.

0
0
10
Ship Pending review

Simple Chat

A TUI-based chat client and server for local networks, featuring encryption and ssh-like host configuration.


simple-chat lets you chat with others on the same network (a LAN, a tailscale tailnet or similar) via a simple terminal UI.
It requires no classical internet connection (in the sense that you have to be able to reach the world wide web like google.com or similar), no accounts and no third-party servers. You control everything, and you’re the only one who sees the messages unencrypted.


How to get it working quickly

Begin by downloading the executable. Then, open a shell in the directory where the simple-chat or simple-chat.exe file is located (or open one via the start menu and cd into the folder where the file is). You will also need multiple shells for this; at least two (one server and one client) but I recommend testing with three to have two clients join onto one server.

First, you need to create a server:

./simple-chat server --new <name>

You will get an encryption key and a QR code. You need to copy or note down the encryption key. When scanned, the QR code also contains the encryption key.

Then, you need to register the server so the clients can connect to it:

./simple-chat client --add <name> --address localhost

<name> doesn’t need to be the same name you used in the first command, but I still strongly recommend it.

Now, we wanna start the server:

./simple-chat server <name>

You should see something like Running as server on port 42003.

Now, in a second terminal, type:

./simple-chat client <name> --name "Testuser-1"

Then, you should see the TUI pop up. If not, use the README and the docs in the GitHub repo to troubleshoot.

Note: In the above command, we’re using the explicit --name flag because if we want two clients on the same machine, they would try to load the same name from the configs. Therefore, we need to explicitly assign two different names via --name.

If you want a second client, run the following in a third terminal:

./simple-chat client <name> --name "Testuser-2"

Additional Info for the above guide

In all of the commands, you should substitute <name> with the name for the server, for example Testserver. I recommend you use the same name everywhere, although you CAN theoretically give the actual server and the client-saved server a different name.


Thanks for reading all this if you came to this part and have fun! :P

  • 6 devlogs
  • 23h
Try project → See source code →
Open comments for this post

3h 4m 37s logged

Final Touches

Simple Chat is now basically finished after some final touches. Lots of fixes have been made, along with adding more documentation.
Here’s a quick changelog:

  • Fix issue where invalid timestamps would panic all clients which received them
  • Fix key inputs being registered twice on windows
  • Check user name length properly
  • Fix multi-line message drawing logic
  • Add a timeout for the initial handshake (5s)
  • Validate chat messages and user names for control characters
  • Write the timestamp on the server-side to prevent spoofing
  • Fix CTRL+C not quitting the TUI
  • Add a TUI message backlog limit
  • Fix the TUI text input box (wide chars now work properly)
  • Update documentation
  • Fix a few other minor inconsistencies
0
0
14
Open comments for this post

3h 41m 56s logged

Packet Types

After a (way too long) pause, I’m finally working on Simple Chat again. I’ve added a packet enum, which wraps all possible kinds of data communication.


What this means

Basically, now everything that’s being sent has the same interface: serialize and deserialize. You don’t need to worry about what is in this packet, just serialize it, encrypt it and it works.

This means handshakes aren’t loose bytes being sent between client and server and held together by pure hopes and dream (im praying somebody gets the reference), but instead a proper packet, which can also now be encrypted.

Heartbeats

Previously, there was also a big design flaw. If no message was being sent, you couldn’t tell if the others were just not sending anything or had already (uncleanly) disconnected. The TCP stream looked the same in both scenarios. Because of this, I’ve added a Packet::Heartbeat type. It contains no data, and if it gets received by any party, (almost) nothing happens. It’s sole purpose is to keep the stream alive.

Now with this, lets say we’re the server: a client doesn’t send a heartbeat twice. We now know that they’ve likely crashed or not properly disconnected in some other way. Because of this, we can now kick them instead of keeping a connection live.

0
0
6
Open comments for this post

8h 30m 35s logged

Server Rewrite!

I’ve rewritten the server using Tokio instead of Threads for improved performance and less server-side overhead.

What Tokio is

It utilizes Rust’s async implementation with futures to create a much more lightweight alternative to threads. Basically, you can summon multiple tasks and instead of them being full threads with seperate 8MB stacks and so on, they have way less context switching overhead and a much smaller stack per instance.

Other Server-Side Changes

In the initial handshake, the client provides its name to the server. If the server receives a message with the wrong name from that client, it will replace the name with the originally given one.


Encryption

I’m still working on finishing up encryption for all messages and the initial handshake.

3
1
76
Open comments for this post

2h 9m 54s logged

im almost done already what

So… it kinda works????
The client AND server, the TUI, the new message structs and all of that work now.

Changelog

  • Reworked Message Struct(s) and multiple message types (Chat, System)
  • Proper connect handshake
  • Rework server to use SystemMessages to broadcast client connect/disconnect events

What’s planned

  • A config file to store your name (~/.lan-chat)
  • Encryption (between clients)
0
0
13
Open comments for this post

1h 6m 40s logged

TUI Time! :)

The TUI works now, I only need to polish it (stuff like scrolling when message box is full)


Additonally, I’m planning to add a special kind of message that the server sends on client join/leave events.

0
0
14
Open comments for this post

4h 27m 44s logged

New Project!!

This is LAN Chat, a TUI chat messenger to use in the same LAN as others.

Build using Rust with ratatui (a TUI library)


Currently, I have a working client/server system, which sends serialized message structs. Next up, I’m gonna work on getting the TUI running.

0
0
64
Open comments for this post

3h 38m 43s logged

DebugWrite syscall!

The userspace process is now able to ask the kernel to write log messages! This means that finally, you can actually properly do something from the userspace program.


Next, I’ll be implementing userspace page fault handling

0
0
11
Open comments for this post

4h 8m 42s logged

Syscall Dispatching, Syscall returning and Exit Syscall

Finally, after all this time: User programs can now interact with the kernel!! :)


Changelog

  • Added Syscall enum which represents all possible syscalls and their respective IDs
  • Added SyscallArgs whichs contains syscall number and args in an architecture-independent format
  • Added SyscallResult and SyscallError
  • Added syscall dispatching logic
  • Fixed kernel data leaking into userspace via initial_userspace_jump by clearing CPU registers before the jump

the end of stardance is approaching… i wish all of you a lot of fun for the last 9 days.

0
0
10
Open comments for this post

7h 13m 52s logged

Syscalls!

Changes so far:

  • Added gs_info struct, which saves the user stack and holds a pointer to the kernel stack
  • Added syscall::init which enables the syscall/sysret instructions and sets up the STAR, LSTAR and SFMASK registers
  • Added syscall_entry which builds a UserFrame (a snapshot of the CPU registers on syscall entry) and calls syscall_dispatch
  • Added syscall_dispatch, which currently logs the received syscall number and halts (the system call return path is not implemented YET)
  • Added UserFrame which holds a CPU register snapshot which gets taken upon entering the kernel
0
0
7
Open comments for this post

7h 39m 28s logged

ELF Loading Refactor and improved build system

After a week of basically no progress, I’ve finally finished moving the ELF init sequence from main.rs into the proper methods under the sched subsystem.

I’ve also simplified and improved the build system (build.py/docs.py). They now use a shared lib.py to avoid code duplication. Also, build.py got a few changes:

  • Removed build cache (cargo already does it)
  • Swapped console restore for proper serial control char output filtering

Bug Fixes

The docker permissions have been fixed, as under Linux, root would own parts of the build files which is of course annoying as you can’t easily interact with those files then.

The github-hosted rustdoc was failing to build as it could not find USER-BINARY, so I just faked user-binary via touch user-binary.

time to go to bed yippeeeee :D

0
0
9
Open comments for this post

6h 7m 7s logged

Finally… after all that work, we’re back!!

After almost 30 hours of work over the last week, I’ve finally finished the big refactor.


Changelog of the refactor

  • Rewritten kprint backend
  • Restructured KSTATE
  • Hit my head on the wall A LOT
  • Added a few new datatypes and macros
  • Optimizations all over the place
  • Added quite a few TODOs (more work 🥲)
  • Restructured the source tree
  • Changed the entrypoint (kernel now starts in an arch-specific kernel_entry, which builds a BootInfo and hands to kernel_main)
  • Moved the userspace/ELF stuff from main.rs into the sched subsystem
  • Rewrote the UART driver

… and so much more.


and of course i organized it all into one slightly large 144 file commit :)

0
0
11
Open comments for this post

9h 13m 28s logged

Big changes are still happening

oh boy this is taking way too long


Rewritten kprint

kprint, the kernel print macro has been fully rewritten. The public macros have not changed, but internally, the macros now:

  • Construct a kernel writer
  • Call core::fmt::write on the kernel writer

The kernel writer collects the text of all calls made to it using core::fmt::write in an internal buffer, and on drop, it pushes a new entry into the kernel log ringbuffer in a format that is (currently only partially) syslog-compatible. Then, the kernel writer dispatches a log header and the full log text to all active log recievers (like UART or the basic framebuffer).

New Types

  • PackedU8: A type which wraps all the bitwise logic for packing multiple pieces of data which are less than one byte into a byte
  • UncheckedCell: A datatype with interior mutability, which can be uninitialized via MaybeUninit. When the cargo feature debug-checks is enabled, this type also checks for double initialization and uninitialized access
  • NicheCell: A datatype with interior mutability, which can be uninitialized via Option. This datatype is designed to be used with values that can take advantage of niche optimization (where the datatype T has a value which is guaranteed to be invalid, like a NonZeroU64 being zero)

and oh boy, the big refactor: redid kernel source tree, ... commit is gonna be one hell of a mess…

0
0
7
Open comments for this post

7h 59m 42s logged

Big changes upcoming…

I’ve decided to change what the kernel aims to be and how it will work. I’ve dropped full linux drop-in compatibility in favour of creating a clean, modern and optimized kernel which tries to fix design mistakes other kernels have already made.


New Design

The kernel will be POSIX-compatible, feature a namespaced VFS to seperate virtual files or devices like /dev/sda, /sys/class/hwmon, /proc/self/ and more from actual files. It will have namespaces like fs:/ for the actual file system, dev:/ for device files (like dev:/sda), vdev:/ for virtual devices (like vdev:/urandom) and more, including hw:/ for a modern hardware info interface, log:/ for a standardized logging interface and reg:/ for a global registry-like configuration system. Internally, the kernel will also feature KSTATE, a central static struct for storing everything the kernel uses. Also, ther kernel will move from strictly limine to a dynamic bootloader format, where each architecture has its own kernel_entry, which prepares the CPU and kernel into a common state (long mode, paging, …) and builds a standardized BootInfo struct, which then gets handed to kernel_main when it is called.

New Source Tree

I’m also currently working on massively reorganizing and cleaning up the kernel source, by for example standardizing where arch-specific code lands, categorizing and organizing the code better and more. Also, the kernel print system (kprint) is currently being rewritten to improve performance and integrate it into KSTATE.

0
0
1
Open comments for this post

5h 42m 32s logged

Refactoring hell is here again!

Before I start on syscalls for the userspace binary, I’ve decided to first get most TODOs done, like updating the safety comments and rustdoc, refactoring the ELF loading stuff to properly utilize KSTATE::procs, proper error delegation and more.


I’ve finally finished refactoring the TSS, GDT and IDT properly by removing the struct wrapper for GDT and IDT, getting rid of all the Onces and more. Additionally, I refactored KSTATE::cpu to be an arch-independent container of arch-specific structs.

holy crap ive got so much left to refactor… and the new bodycam update just came out i wanna play but am stuck doing kernel stuff and some other stuff :’)

0
0
29
Open comments for this post

6h 16m 21s logged

@everyone… WE HAVE USERSPACE!!! 🎉 :D

This is something I’ve been waiting for sooo long… a binary is actually executing in USERSPACE now (ring 3 and stuff)!!!


How it works

Currently, the code is extremely hacked together, or “gepfuscht” as we like to call it in Austria ;). Basically, what should be cleanly integrated with KSTATE::procs, should use a proper userspace and kernel stack and so on, doesn’t. All of this of course won’t stay like that, but it currently is like that to just get something in userspace running. The one thing that is actually done cleanly is the ELF validation and definitions, which live under src/elf. There is a struct representing the ELF header, an ELF PHDR and also an enum for ELF validation errors. All other logic was just thrown into main for now, like creating the processes address space, mapping its memory, copying the binary into the mapped memor, setting up its stack and actually jumping to it.

The current testing binary

Written in good old assembly of course, the test binary currently has the job to cause some sort of CPU exception, as syscalls or any other way to interact with the kernel or output something doesn’t even exist yet.

The current program manages that via just running the hlt instruction, which, when run from userspace (Ring 3) causes a general protection fault (#GP). This is because a userspace program shouldn’t just be able to halt the CPU or disable interrupts (via the cli instruction). That would defeat the whole purpose of rings and kernel/userspace.

The userspace binary currently lies under src/user-binary.asm, next to src/kernel/. It is build via running docker exec ferrite_os /ferrite_os/build-user-binary.sh. NASM was also added to the Dockerfile to make building the assembly file possible.

0
0
16
Open comments for this post

4h 10m 22s logged

Virtual Memory Management is complete! (for now)

I’ve added methods for mapping pages and VMA together. I’ve also improved the kernel VMA granularity and added a kernel paging remap step to mm_init() which applies the kernel VMA permissions to the kernel pages. This is only intermediate until I switch to the linux boot protocol.


Next up, I’ll be adding a (very basic) ELF loader!

0
0
15
Open comments for this post

7h 57m 7s logged

Lots of stuff

 
Changelog for the last two days

  • Fixed the clean option in build.py
  • Add kernel VMA setup method
  • Update rustdoc in several places
  • Add panic types to kernel_panic output
  • Move SIMPLE_STATE out of main
  • Improve per-arch code layout

… and an INSANE amount of optimizations, including:

  • Marking certain code paths as cold (which signals the compiler to expect that the code path is unlikely to occur and optimize accordingly)
  • Remove a lot of overhead introduced by Once and similar by swapping them for UnsafeCell<MaybeUninit<T>> and others and taking advantage of things like niche optimization
  • Using bit-packing instead of seperate booleans for state tracking

Next up, I’ll be doing some more work on the VMM and finally add the full mapping methods, which map pages and VMAs together.

0
0
15
Open comments for this post

4h 32m 53s logged

AddressSpace and VMA data structs done!

I’ve introduced a struct called AddressSpace which contains the virtual address of the root page table (the PML4 on x86_64) and a BTreeSet of VMAs (virtual memory areas).

A BTreeSet is basically a type of set which organizes data in a tree-like structure (in a B-Tree structure to be exact) with a time complexity of O(log n) for inserting, removing and searching for elements.

A VMA (virtual memory area) is an area in memory with specific flags, such as READ, WRITE, EXEC or USER. One of their main uses is determining what to do on a #PF (page fault): should we allocate a page here (lazy-mapping)? Or was this truly a faulty operation like trying to execute READ-only memory?


Also, I briefly attempted to switch over to the linux boot protocol, but I decided to do that sometime later, because it is slightly complicated and definitely did not break everything ;)

0
0
12
Loading more…

Followers

Loading…