-
Notifications
You must be signed in to change notification settings - Fork 14k
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.
- Compare Section 7.6 "Stack Pointer" of the ATmega640/V-1280/V-1281/V-2560/V-2561/V datasheet
- 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 New format_args!() and fmt::Arguments implementation #148789 has reworked the formatting machinery to use the low bit of a pointer to
core::fmt::rt::Argumentas a tag.- because the
Arguments are allocated on the stack, a misaligned pointer is embedded in thefmt::Arguments, which then gets interpreted as the string literal case – printing kilobytes of garbage onto the serial port
- because the