-
Notifications
You must be signed in to change notification settings - Fork 363
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
Is read_volatile on uninitialized memory really undefined behavior? #2807
Comments
Rust cannot guarantee uninitialized On platforms without such constraints this may be safe, but the rules aren't clear. See also rust-lang/unsafe-code-guidelines#152. |
We also specifically asked LLVM if they could guarantee that volatile reads are frozen, and they said no. (Not to mention that an RFC would be required to expose freezing semantics in Rust.)
I'm afraid that's not how this works. Just because the compiler currently happens to work like this, doesn't mean anything. The only thing that matters is what is explicitly stably documented. Right now, whether a read is volatile or not makes no difference for whether a program has UB, ever.
Then you must use |
More context (copy-pasted from URLO thread): I am writing a logger for an embedded system (STM32, but the details don't really matter here). The logger writes the log messages to a ring buffer in system memory (RAM), which can then be read at a later time. That's most definitely about accessing memory that is shared with other devices and the trouble here is how to safely access memory which Rust (and Miri) believes to be unitialized, but which is not, in reality, uninitialized. You can not do much of an embedded work without such facility and it would be nice to have some support on Miri side for these needs. |
Hello, drive-by comment from a libs contributor / person who does an unfortunate amount of FFI: We have discussed adding support for "allocating" things (really, declaring that a certain location is known to be shared memory) in Rust, and we do intend to add it to the language/standard library. It's mostly a matter of interface design. Yes, it's true that Miri doesn't offer much support for it right now. If it has a known fixed address at program link time, you can probably use an |
I am using
In fact the memory is not shared with any other device. It is "normal" memory, placed into a linker section that prevents the initialization code from clearing it at program startup. You might think about it as memory written by another device before the program was started if you wish. However, once the Rust code is running it has exclusive access to the memory location and no special handling is required. What I need is a way to tell the compiler that the memory has been pre-initialized before the program was started and the compiler must not make any assumptions when first reading the memory. If neither
From the docs: "An immutable [extern] static must be initialized before any Rust code is executed". I cannot guarantee that the contents of the memory location are valid before Rust code start, i.e. I have a bool in my struct and after power-on the value of that memory location might be a random bit pattern, so neither 0 nor 1. So maybe I need an |
I've run a few experiments (with the compiler, not miri) to check whether First I modified Ralf's example from https://www.ralfj.de/blog/2019/07/14/uninit.html and introduced a use std::mem;
fn always_returns_true(x: u8) -> bool {
x < 120 || x == 120 || x > 120
}
fn main() {
let xu: mem::MaybeUninit<u8> = mem::MaybeUninit::uninit();
unsafe { xu.as_ptr().read() };
let x = unsafe { xu.assume_init() };
assert!(always_returns_true(x));
} This still results in undefined behavior: Then I changed the read to use use std::mem;
fn always_returns_true(x: u8) -> bool {
x < 120 || x == 120 || x > 120
}
fn main() {
let xu: mem::MaybeUninit<u8> = mem::MaybeUninit::uninit();
unsafe { xu.as_ptr().read_volatile() };
let x = unsafe { xu.assume_init() };
assert!(always_returns_true(x));
} This executes correctly. |
You are relying on the result of undefined behaviour, LLVM just accidentally produces the assembly you desire in the second case
You should initialize your memory before reading from it. With miri you can use |
For your use case I did expect the following to work: extern "C" {
#[link_section = ".uninit"]
static mut BUFFER: MaybeUninit<RingBuffer<8192>>;
} That is rather than defining the This is not something miri can emulate without modifying miri to handle the |
This doesn't compile because |
I feel like this issue started off going in the wrong direction. The answer to the initial question is definitely "yes", but I'm not sure that it is relevant to the data structure you are trying to implement. It sounds to me like the memory for a |
Right
You could define it in a linker script. Or maybe just |
The generated program is a random sequence of bytes that just happens to take the shape of a seemingly working program by accident. Such is the joy of code that causes UB. You cannot deduce anything from what happens when you execute a program with UB, since that act is by itself meaningless. You need to establish that your program has no UB before making any inference based on what you see the program do after it came out of the compiler.
Where is the ring buffer created and initialized? I found this comment
but it says that the section is not initialized so this can't be enough yet. So where is this pre-initialization you are talking about happening?
That sounds like an So using read_volatile makes no difference here. And it seems like your memory is actually initialized, so you don't even need If you want to execute such code in Miri, you have to write a suitable test harness that emulates the effects of whatever outside-of-Rust magic would usually happen. Note that why anyone would initialize their statics in such a complicated way is way beyond me so I can't really help with any of the details here, but as @saethlin noticed there's a definite XY problem here. Your question isn't about read_volatile or uninit memory, it is about linker script tricks that are outside of the realm of the Rust language. That is all based on the assumption that the static is indeed pre-initialized somehow. But I haven't found a trace of that yet. As far as Rust is concerned, if you declare a static to be initialized with |
As I understand it the intention is that whatever logs left over from before a reboot show up. This can be modeled in the AM as the contents getting saved right before a reboot and being written again after the reboot before |
The ring buffer is initialized in the init function and nowhere else. It is not accessed by any external code, hardware peripheral, memory chip with special behavior or anything else. The only special thing about it is the It works like this: When the embedded system is first powered on, the memory contains a random bit pattern. The processor starts at the entry point and eventually Now, and here comes the interesting part, when the embedded system is reset (without power off, for example by its watchdog) the processor restarts execution at the entry point with its registers cleared. However, memory (RAM) is not cleared. This is absolutely normal behavior for memory. The Note: currently I am writing the same values back into memory if the validation has passed. This should not be necessary, but I am doing it in the hope that it avoids UB. Ideally I would just read the actual memory contents, verify it and then, if verification passes, call Why am I doing this? The ring buffer contains log messages that should not be lost during a system reset. Do I need I think so, because the bit pattern at power up might be invalid for the boolean field Is it crazy? No, not in the embedded world. There a many cases where it is necessary to preserve data during reset. For example, a stepper motor controller might want to preserve the physical position of an axis during reset (or even firmware update) because it has no sensors to determine the position and knows it only by counting how many steps it has done. |
I think a reasonable approach in your situation would be to initialize the memory on Then you won't really need volatile as your memory will be initialized from Rust's point of view. For state synchronization you may consider using |
Even taking this as a given, calling It sounds like what you need here is a way to get frozen memory or to freeze existing uninit memory, which Ralf suggested far above. |
I see. Thank you! I assume this will work but is kind of unsatisfactory. It will require a working C compiler for every target, quite complicated build steps for cross compiling and the struct must be translated to C so that its size matches exactly with the Rust counterpart. This might be becoming off-topic, but if this is possible in C and LLVM implements a C frontend, why can't we do the same directly from Rust, for example by using a special attribute? |
But isn't a lot of memory quite volatile? DRAM requires continuous refreshing. You are talking about some kind of nonvolatile memory, or else SRAM that has the minimal power flowing it to retain full data remanence. Rust's semantics can't assume the presence of nonvolatile memory, which is part of why this seems so complicated to express despite being "simple".
Hmm... you could describe the section in pure If that doesn't work, probably we should support extern {
#[link_section = ".used_section"]
static BUF: Buffer;
} or have another way to bless this pattern, somehow. |
If you reboot rather than shutdown and start up again later, the memory controller likely isn't offline long enough (if it is offline at all) for the DRAM to lose it's contents, right? |
Okay I think we first and foremost have a Rust Abstract Machine / @rust-lang/opsem question here, not a Miri question: If a mutable (or interior mutable) static is initialized in Rust to a certain value, then can the Rust AM assume that it does have that value when the program starts? Or is it okay to have some "before main setup" actually change the value of that static? I would say for an immutable static, this is clearly UB -- using linker script tricks like what has been described here is just not allowed for those statics. But for statics that can be mutated, we already can't in general optimize assuming their values did not change, so... it seems reasonable to allow this? With this way of thinking about the question, obviously Miri has no way of knowing that you are modifying the value of the static before Rust code starts running. On the Rust side you are saying this static is filled with uninit memory, but then you are making some outside-of-Rust assumptions to justify that actually when the program starts the static has a different value, in particular that the bytes now are initialized. So I don't think there is anything actionable on the Miri side here, and the discussion (with the clarified question) should move to https://github.com/rust-lang/unsafe-code-guidelines/ . |
Moved to rust-lang/unsafe-code-guidelines#397. |
Hi!
I must read memory that is considered uninitialized by Rust. For that purpose I am using the
core::ptr::read_volatile
function. However, when running the program under miri it complains with the following error:The code is available at https://github.com/surban/defmt-ringbuf-miri.
See also this discussion thread for more background information. The conclusion is that
core::ptr::read_volatile
on uninitialized memory is okay because the compiler cannot make any assumptions about it, i.e. it could be IO memory that changes with every read.Thus, to be consistent with the compiler's behavior, miri should allow reading of "uninitialized" memory through
core::ptr::read_volatile
.The text was updated successfully, but these errors were encountered: