NOTE – Creating Our Own Fibers-1

There seems to be an issue in macOS using such a small stack. The minimum for this code to run is a stack size of 624 bytes. The code works on the Rust Playground, at https://play.rust-lang.org, if you want to follow this exact example (however, you’ll need to wait roughly 30 seconds for it to time out due to our loop in the end).

Then let’s add a struct that represents our CPU state. We’ll only focus on the register that stores the stack pointer for now since that is all we need:
#[derive(Debug, Default)]
#[repr(C)]
struct ThreadContext {
    rsp: u64,
}

In later examples, we will use all the registers marked as callee saved in the specification document I linked to. These are the registers described in the System V x86-64 ABI that we’ll need to save our context, but right now, we only need one register to make the CPU jump over to our stack.

Note that this needs to be #[repr(C)] because of how we access the data in our assembly. Rust doesn’t have a stable language ABI, so there is no way for us to be sure that this will be represented in memory with rsp as the first 8 bytes. C has a stable language ABI and that’s exactly what this attribute tells the compiler to use. Granted, our struct only has one field right now, but we will add more later.

For this very simple example, we will define a function that just prints out a message and then loops forever:
fn hello() -> !
{
    println!(“I LOVE WAKING UP ON A NEW STACK!”);
    loop {}
}

Next up is our inline assembly, where we switch over to our own stack:
unsafe fn gt_switch(new: *const ThreadContext) {
    asm!(
        “mov rsp, [{0} + 0x00]”,
        “ret”,
        in(reg) new,
    );
}

At first glance, you might think that there is nothing special about this piece of code, but let’s stop and consider what happens here for a moment.

If we refer back to Figure 5.1, we’ll see that rsp is the register that stores the stack pointer that the CPU uses to figure out the current location on the stack.

Now, what we actually want to do if we want the CPU to swap to a different stack is to set the register for the stack pointer (rsp) to the top of our new stack and set the instruction pointer (rip) on the CPU to point to the address hello.

The instruction pointer, or program counter as it’s sometimes called on different architectures, points to the next instruction to run. If we can manipulate it directly, the CPU would fetch the instruction pointed to by the rip register and execute the first instruction we wrote in our hello function. The CPU will then push/pop data on the new stack using the address pointed to by the stack pointer and simply leave our old stack as it was.

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Post