oxidecomputer / oxidecomputer/hubris
Kernel refuses to start if task's initial stack frame spans regions
@cbiffle is already working on this.
Since Dec 13, 2024.
- Dominant language
- Rust
- Stars
- 3.6k
- Forks
- 239
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 23
Description
The exhubris tools are turning out to be a pretty good kernel torture test. 😄
During the reinitialize operation that starts (and restarts) a task, the kernel (on ARM M-profile at least) needs to deposit an initial stack frame in the task's stack memory, from which the hardware will load the task's initial register values. Before we do that, we "zap" the stack, overwriting it with a predictable pattern to help monitor a task's stack usage high-watermark.
The code in question is here: https://github.com/oxidecomputer/hubris/blob/8437fbd30aa129bb6e6fb66fe274be719c62ee8b/sys/kern/src/arch/arm_m.rs#L298-L322
That code assumes that the memory region containing the topmost word in the stack (initial_stack_ptr.saturating_sub(4)) also contains the the entire stack, because the kernel uses region.base to define the place to start zapping.
If the initial stack frame (104 bytes on Cortex-M4, thanks largely to the FPU) does not fit in the region that contains the topmost word of the stack, you instead get an invalid USlice constructed using region.base and a large negative number. (If you're building with overflow checks enabled, you get a subtraction with overflow panic. I disabled overflow checks to help find the real problem.) USlice rightfully rejects this as nonsense, which the code here really wasn't expecting, since it calls unwrap_lite on the result. Panic.
This code dates to the early days of Hubris when a task would always have a single contiguous RAM region containing the stack. In mainline "Oxide Hubris" that's still true in practice: we mostly apply the region-splitting allocator to flash.
The exhubris tools split both RAM and flash, deciding which to split next based on the potential cost of fragmentation given their sizes. This means I wound up with a task with a 256 byte stack split over no fewer than five regions (sizes: 32, 64, 64, 64, 32). In that case, it takes the last three regions to cover the 104-byte initial stack frame, and hilarity ensues. Where by "hilarity" I mean "the application refusing to start."
Interestingly, the actual code for writing the frame (just below) works fine if I disable the stack zap code, because it doesn't try to be clever with regions. So, the zap code itself needs to be improved to handle more elaborate RAM region splits.
I may land an initial fix that just makes the zap code optional, since the stack zapping method is inexact anyway (#1872).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.