doc/wg/core/notes/core-notes-2021-11-05.md
u8 to a reference to a
Cell<u8>.u8 slices and we wanted to single
source code and have everything use ReadOnlyProcessBuffer. I added a casting
method to do it, which is good, but running that through Miri we are seeing
that is what is triggering this Miri issue. It's logically representing
something that was already existing with applications and the kernel today --
it's just hard to test that in a unit test that Miri can see because it's
across the kernel boundary. But the conversion function is representing what
we do in an application. What it boils down to, what it doesn't like, is if
you have an immutable reference to a u8, it is undefined behavior to cast
that to a Cell<u8>, even if you don't use the mutability properties of
Cell. So like we do this, we have a read-only byte wrapper which is used by
the read-only byte slice, which is provided by the read-only process buffer.
At the end of the chain, the read-only process byte contains a Cell<u8>. We
transmute our references and our pointers to this, and Miri is complaining
that we are exposing a vector to try to mutate immutable memory. This could be
a pointer into immutable memory, like flash memory, and we could theoretically
write safe code (without the unsafe keyword) to do Cell.set and that would
be undefined behavior. That is it in a nutshell, and we've been going through
a few different solutions to try to solve it. The constraint is that we're
using Cell because we want to tell Rust that this memory can change
without Rust knowing about it -- just because we have an immutable reference
to it doesn't mean it can't change. It could change in the application memory
because the application could share a mutable buffer it could change. We do
need that property of a Cell because we don't want to have an immutable
reference to memory that actually can change, and the Miri issue is we don't
want to cast an actual immutable reference to a cell. Those are the two
conflicting constraints and we're going back and forth. It's looking like the
way of solving both the Miri issue and to maintain soundness in terms of
having an immutable reference with that memory changing under the hood is to
use raw pointers. Alyssa, who is @kupiakos on GitHub suggested an exotic
solution with zero-sized types and slices and pointers. It is exotic, and so
we want people's opinion on it. Is it worth solving this Miri issue? I feel
like yes, but we're not using incorrectly so we're not invoking the undefined
behavior, and if the pointer solution will all be contained in that file
processbuffer.rs. If it's really hard to understand is it worth doing
because it solves both issues issues, do we just comment a bunch? So what is
the way we want to solve this or do we want to solve this.u8 to a reference to a Cell<u8>. That is what Miri sees and
complains about. We shouldn't ever do that because that allows safe code to
try to mutate that immutable reference, which is not allowed and would cause
undefined behavior. Even wrapping the reference to a u8 in a reference to a
Cell<u8> is what is triggering Miri to say that is undefined behavior,
because you are allowing safe code to mutate it which is UB. Does that help?set method, only a get method.Cell is based off UnsafeCell. UnsafeCell is the language built-in
feature that marks this kind of interior mutability that lets Rust know the
memory I'm representing can change under the hood even with an immutable
reference. There is no other thing beside UnsafeCell or something based off
UnsafeCell. Even wrapping a mutable reference to a u8 in an UnsafeCell
will trigger Miri's complaining about undefined behavior because through an
UnsafeCell you can then mutate it.Cell but the way we're
treating the Cell and the way we're limiting its API surface is not
something that should -- and I say should because I'm not too deep in the
compiler internals of Rust and how it translates to opitimizations and
assembly -- but we are limiting the API surface we have on our cell type to be
only immutable accesses to the underlying memory. Because the Miri issue
persists even if we are accessing it through an UnsafeCell using explicit
unsafe methods -- not unsound but unsafe -- we have to be aware of what
we're doing with the returned reference, as far as I understand this issue
it's more a warning issued by Miri. It says if we were to strictly follow the
borrow checker rules with the underlying borrow stack which is implemented in
the Rust compiler, it could lead to unsound behavior if we were to use the
interfaces correctly.volatile. This thing can change underneath us, we have read
access to it, so why don't we write an API for that? Not use Cell or
UnsafeCell.*const u8, it uses a zero-sized type and the
address of that zero-sized type is a proxy for the slice you are going to use.
So it's a zero-sized type that has an address in memory and that is what is
represented in the buffers. When you slice it, you have to walk the zero-sized
type forward and make a new zero-sized type when you're trying to slice it and
then dereference it. It is effectively using pointers under the hood.Index trait. The Index trait is the one executed on your thing when
you use the [] operators in Rust. It requires you to return a reference to
something, so you cannot return an integer or pointer or safe wrapper around a
pointer because that would need to be allocated on the caller's stack, which
doesn't work when you return a reference.[] anymore and would have
to change to a method like .index(). It would only be a syntactical change,
but it would be a change which propagates through the entirety of the kernel.unsafe is one of her specialties she likes to look into. I have confidence
her solution will be good and will be sound. We should absolutely still vet it
and get more opinions, but I have confidence that it will actually solve the
issue and we won't have to change the APIs.UnsafeCell around process memory, particularly process memory in flash which
is immutable, but the UnsafeCell would potentially -- as far as Rust is
concerned -- allow mutation using unsafe code. That is not something which
Rust can reason about so we're touching on this gray area where the Rust
concepts do not map cleanly onto what the machine exposes. It is the purpose
of unsafe code to define how those things interact.unsafe at those times you need to access memory that others outside Rust's
control can mutate.Sync and Send constraints for the buffers
which we will transfer across the boundary and make sure the kernel and user
applications respect this. Some would like an implementation of like Rc or
Arc in a way that wouldn't have such burden as it would have in the std
crate but suitable for embedded things, some simple flag maybe attached to
every buffer indicating whether it can be used or not. The same implementation
is shared by both kernel and userspace, so a mutual respect to who is
accessing what at the type level.unsafe but
there will always be unsafe when we cross the boundary.Cell<u8> and a u8 and that did not work, unfortunately.enum on top of
that. Then we're never actually creating a process slice of kernel memory, we
just have a composite type that can encompass both user memory and kernel
memory if we know we need to treat them differently on the type level.UnsafeCell instead? That would make it clear that
we have an unsafe interface and need to use unsafe to access the data.The Flash HIL discussion was tabled until next week because there wasn't time to discuss it.
elf2tbf,
tockloader works. The challenge is the current semantics of the main header
and TBF loading in the kernel are such that if you load a binary kernel before
this update and somebody loads an application that has a program header, then
when the Tock kernel loads the binary it will skip the header, see there isn't
a main header, and default the init function pointer to be 0. In practice, if
you install a binary with a program header on an old kernel, it will generally
crash immediately, and the kernel can't tell that this has happened. So I
think there are a couple options, for example instead of a program header we
could just have an extra "binary ends" header. I don't like that because it is
a "required optional" header. Another thing we could do is change the
semantics about program headers, you can't have neither.elf2tab make the
switch, or will most apps be built with the main header?elf2tab -- you do --footers and it
inserts a program header. Whether all the makefiles use --footers is a
separate question. I was thinking yes, but I wanted to remain
backwards-compatible.libtock-c and libtock-rs makefiles. If we don't make it a default I
suspect it won't be an issue. I'm inclined to think we can sort of avoid this.
I also think it would be fine to have both, and just have newer kernels ignore
the main when both are present.elf2tab but the kernel sees it as
optional. What is an example where you don't have the main header?