rust-lang / rust-lang/rust

[AVR] core::fmt prints out-of-bounds memory on microcontrollers with more than 128KB of program memory

Open
#149,223 8 comments 5 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-LLVM C-bug O-AVR T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

The reproducer is available ready-to-go at https://codeberg.org/mei-b/farbix, commit 26684d7b9339112b9b4fc08ca7c0d973341ce929. The code itself is as follows:

#![no_std]
#![no_main]

use arduino_hal::{Peripherals, port};

panic_serial::impl_panic_handler!(
  arduino_hal::usart::Usart<
    arduino_hal::pac::USART0,
    port::Pin<port::mode::Input, arduino_hal::hal::port::PE0>,
    port::Pin<port::mode::Output, arduino_hal::hal::port::PE1>
  >
);

#[inline(never)]
fn meow1() {
    meow2()
}

#[inline(never)]
fn meow2() {
    panic!("{}", 2137)
}

#[arduino_hal::entry]
fn main() -> ! {
    let dp = Peripherals::take().unwrap();
    let pins = arduino_hal::pins!(dp);
    let serial = arduino_hal::default_serial!(dp, pins, 57600);
    let serial = share_serial_port_with_panic(serial);
    meow1(); // if bug doesn't reproduce on a newer compiler, try calling
             // meow2 directly instead
    loop {}
}

I compile this with the following settings in .cargo/config.toml:

[build]
target = "avr-none"
rustflags = ["-C", "target-cpu=atmega2560"]

[unstable]
build-std = ["core"]

The dependencies involved are as follows:

[package]
name = "farbix"
version = "0.1.0"
edition = "2024"

[dependencies]

[dependencies.arduino-hal]
git = "https://github.com/Rahix/avr-hal"
features = ["arduino-mega2560"]
# main branch at the time of writing
rev = "6de651a2303ad1f392fc628675af2372632df4fb"

[dependencies.panic-serial]
git = "https://github.com/nilclass/panic-serial"
rev = "409d81c6f5d36d2fc73e4df0e0b0a09e10989b94" # PR #1
features = ["full"]

[profile.release]
panic = "abort"
lto = true
opt-level = "s"

Note that these particular dependencies aren't central to the issue – it's just the easiest way I knew of for creating a self-contained example for what's inherently bare-metal target.

To run this example without physical hardware, clone and compile https://github.com/buserror/simavr, and then run something along the lines of:

cargo build --release && ~/src/simavr/simavr/run_avr -m atmega2560 target/avr-none/release/farbix.elf

Expected result:

Panic at src/main.rs:21:5: 2137

Actual result:

Panic at src/main.rs:21:5: �./home/mei/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/num.rs...
Panic at ........"...Y.......................C.�.00010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899called
`Option::unwrap()` on a `None` valueE.n.^[garbage continues]
Meta

rustc --version --verbose:

rustc 1.93.0-nightly (53732d5e0 2025-11-20)
binary: rustc
commit-hash: 53732d5e076329a62f71d3c6901886ce8a71e812
commit-date: 2025-11-20
host: x86_64-unknown-linux-gnu
release: 1.93.0-nightly
LLVM version: 21.1.5

Diagnosis

The breakage bisects to #148789. Moreover, the issue only occurs on the atmega2560, and doesn't reproduce on e.g. the atmega1280. The bug occurs because:

  • the flash memory of the ATmega2560 is big enough that a 16-bit program counter is not enough. Because of this, the return addresses on the stack are 3 bytes long:
    • Compare Section 7.6 "Stack Pointer" of the ATmega640/V-1280/V-1281/V-2560/V-2561/V datasheet

      The Stack Pointer is decremented by one when data is pushed onto the Stack with the PUSH instruction, and it is decremented by two for ATmega640/1280/1281 and three for ATmega2560/2561 when the return address is pushed onto the Stack with subroutine call or interrupt.

  • on AVR, LLVM ignores any alignment requirements passed to alloca, and the generated code never concerns itself with stack alignment
    • normally, this is fine, because the AVR instruction set doesn't have any instructions that would care about the alignment of memory addresses
  • however, the aforementioned Rust PR #148789 has reworked the formatting machinery to use the low bit of a pointer to core::fmt::rt::Argument as a tag.
    • because the Arguments are allocated on the stack, a misaligned pointer is embedded in the fmt::Arguments, which then gets interpreted as the string literal case – printing kilobytes of garbage onto the serial port

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by building the linked farbix reproducer for the avr-none atmega2560 target and running it with simavr, comparing the expected and actual output. Read the formatting machinery changes in #148789, especially the pointer tagging for core::fmt::rt::Argument, alongside the reported AVR stack and return-address behavior. Done means formatting the panic value no longer treats the misaligned argument as a string literal and produces the expected output.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers, embedded-iot
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.