Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: backup/restore registers #154

Closed
wants to merge 16 commits into from

Conversation

kennystrawnmusic
Copy link

@kennystrawnmusic kennystrawnmusic commented Apr 29, 2020

This should make blog-os:post-13 a bit easier to write.

This should make phil-opp/blog-os:post-13 a bit easier to write.
Compiler was throwing a fit upstream; see if this makes a difference
Was giving "malformed inline assembly" for Windows but not Linux (which I've got as a main host OS on my end) ― weird
Hopefully this solves the build issues
@kennystrawnmusic
Copy link
Author

When I run cargo +nightly fmt -- --check on my own end with identical code it succeeds ― why is it failing upstream?

Hopefully this gets us to pass the formatting checks
Let's see if this solves the formatting errors…
Merge all `unsafe` blocks into one per function ― there was just too much repetition there, and that repetition may be what was causing the format failures.
@phil-opp
Copy link
Member

Thanks for the pull request! I'm not sure whether this approach works though. The problem is that the method code itself might overrwrite the register content before we backup it.

@kennystrawnmusic
Copy link
Author

kennystrawnmusic commented Apr 29, 2020

That’s why each thread stack is protected by a mutex on most platforms — just take a look at Linux’s kthread.c for example: each stack is protected by, in that case, invoking kthread_create_lock as defined in the included linux/mutex.h file to force the CPU to avoid overwriting anything until the context switch has taken place, and in Rust we could probably use spin::Mutex to achieve a similar result.

As another reference, this is how Redox does it — in that case, by:

  1. Disabling interrupts
  2. Loading register content
  3. Enabling interrupts again
  4. Actually switching context

You could combine Step 1 with x86_64::instructions::hlt() too, for added protection before the switch.

@kennystrawnmusic
Copy link
Author

kennystrawnmusic commented Apr 30, 2020

For the record, this is very similar to Nikos Filippakis’s approach.

@phil-opp
Copy link
Member

It has nothing to do with the stack. You're taking the current value of the CPU registers in the middle of a Rust function. The Rust function needs CPU registers itself, e.g. to store its variables, so it will probably back up some registers at the beginning of the function and then overwrite it with different content. So at the point when you look at the register values through inline assembly, their content might already have changed.

The blog you linked takes a different approach for saving the registers:

  • It uses a "naked function" that has no prologue to avoid overwriting CPU registers at the start of the function
    • Note that you can only use inline assembly in naked functions, not normal Rust code.
    • Since this is easy to get wrong, many people are proposing to remove support for naked function in favor of external assembly files.
  • It pushes the registers to the stack instead of saving them in local variables. This is required because no local variables can be used with naked functions.

While I think that this approach works, I don't think that adding such a function would be a good idea because it is very easy to get things wrong. The main limitation is that you can only safely call such a function from other naked functions because otherwise the outer function prologue would overwrite the registers contents again. This is the reason why the timer interrupt handler is a naked function too in the linked blog post. (While it seems that this work right now, it's using normal Rust code inside a naked function, which is neither supported nor recommended and can easily lead to undefined behavior.)

@samueltardieu
Copy link
Contributor

There are other reasons why this code would not work:

  • rip is not restored last, so the code after it is restored will never run since execution would have jumped elsewhere already. To put it simply, mov $0, %rip is a jmp $0.
  • Moreover, the saved value for rip corresponds to some place in the middle of context saving. Restoring rip to this value would restart the restored process right at this place and resume context saving instead of restoring the context.

Usually, rip is not restored explicitely via a register copy. An instruction such as iret or sysret sets rip while also doing other things (such as changing the active segment selectors to reflect a change in the privilege level). Also cr3 is often updated at the same time to update the page table location. Some kernels such as Linux also update gs or fs because they use those selectors as base for process- or CPU-specific information.

Context switching is strongly dependent of what your kernel is doing. I am not sure it would ever be possible to devise a universal context-switch function that could be used by all the kernels using this crate.

@phil-opp
Copy link
Member

Closing, since the code does not work this way unfortunately.

@phil-opp phil-opp closed this Sep 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants