Skip to main content

avr_stack/
lib.rs

1// -*- coding: utf-8 -*-
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright (C) 2025 - 2026 Michael Büsch <m@bues.ch>
4
5//! This crate provides helper functions for stack analysis on AVR.
6//!
7//! [estimate_unused_stack_space]:
8//! Estimate the number of stack bytes that have never been used.
9//!
10//! # Initialization of stack space
11//!
12//! The main crate must call the [init_stack_pattern] macro once
13//! to define the stack initialization function.
14//!
15//! On initialization, all of the stack space is overwritten with a byte [PATTERN] by the macro.
16//! The code that does this runs from the linker section `.init4`.
17
18#![cfg_attr(not(test), no_std)]
19#![cfg_attr(target_arch = "avr", feature(asm_experimental_arch))]
20
21/// Memory pattern for unused stack space.
22///
23/// The unused stack space is filled with this byte pattern.
24pub const PATTERN: u8 = 0x5A;
25
26/// Define an `.init4` function to initialize the stack.
27///
28/// This macro should be called once from the main crate to define
29/// an `.init4` function to overwrite the entire stack with [PATTERN].
30#[macro_export]
31macro_rules! init_stack_pattern {
32    () => {
33        #[cfg(target_arch = "avr")]
34        #[unsafe(naked)]
35        #[unsafe(no_mangle)]
36        #[unsafe(link_section = ".init4")]
37        /// Overwrite the entire stack with the [PATTERN].
38        ///
39        /// The stack grows downwards.
40        /// Start from the stack's end and iterate upwards to the stack's beginning.
41        ///
42        /// # Safety
43        ///
44        /// This naked function is run before main() from the .init4 section.
45        unsafe extern "C" fn __avr_stack__mark_pattern() {
46            core::arch::naked_asm!(
47                "   ldi r26, lo8(__bss_end)",   // X = stack end
48                "   ldi r27, hi8(__bss_end)",   // ...
49                "   ldi r17, hi8(__stack)",     // stack begin (high byte)
50                "   ldi r18, {PATTERN}",        // initialization byte pattern
51                "1: cpi r26, lo8(__stack)",     // check if we reached stack begin
52                "   cpc r27, r17",              // ...
53                "   st X+, r18",                // write pattern to stack and inc X
54                "   brne 1b",                   // repeat if not at stack begin
55
56                PATTERN = const $crate::PATTERN,
57            );
58        }
59    };
60}
61
62#[cfg(target_arch = "avr")]
63#[inline(always)]
64fn avr_estimate_unused_stack_space() -> u16 {
65    let mut nrbytes;
66
67    // SAFETY: The assembly code only does atomic memory reads.
68    unsafe {
69        core::arch::asm!(
70            "   ldi r26, lo8(__bss_end)",               // X = stack end
71            "   ldi r27, hi8(__bss_end)",               // ...
72            "   ldi r18, hi8(__stack)",                 // stack begin (high byte)
73            "1: cpi r26, lo8(__stack)",                 // check if we reached stack begin
74            "   cpc r27, r18",                          // ...
75            "   breq 2f",                               // reached stack begin -> done
76            "   ld r19, X+",                            // read the stack byte and inc X
77            "   cpi r19, {PATTERN}",                    // check if the read bytes still matches PATTERN
78            "   breq 1b",                               // if it matches, go on searching
79            "   sbiw r26, 1",                           // undo post-increment on mismatch
80            "2: movw {nrbytes}, r26",                   // copy X (points to last matched address)
81            "   subi {nrbytes:l}, lo8(__bss_end)",      // number of bytes is X minus stack-end
82            "   sbci {nrbytes:h}, hi8(__bss_end)",      // ...
83
84            nrbytes = out(reg_pair) nrbytes,            // nrbytes is output only
85
86            out("r18") _,                               // stack begin high byte
87            out("r19") _,                               // stack content temporary
88            out("r26") _,                               // X low
89            out("r27") _,                               // X high
90
91            PATTERN = const PATTERN,
92        );
93    }
94
95    nrbytes
96}
97
98/// Returns the number of stack bytes that have never been written to.
99///
100/// This function can be called at any time.
101///
102/// This function walks the stack from end to beginning
103/// and checks if the initialization [PATTERN] is still there.
104/// The number of bytes that still contain the [PATTERN] is returned.
105///
106/// The returned value is only an estimate.
107/// If the program code wrote [PATTERN] bytes to the stack, then
108/// these bytes will falsely be seen as unused.
109/// It is assumed that this scenario is unlikely to occur for more than a couple of bytes,
110/// and therefore the estimation is expected to be off by no more than a couple of bytes.
111///
112/// This function does not protect against stack overflows.
113/// If an actual stack overflow occurred, the behavior is undefined.
114pub fn estimate_unused_stack_space() -> u16 {
115    #[cfg(target_arch = "avr")]
116    let nrbytes = avr_estimate_unused_stack_space();
117
118    #[cfg(not(target_arch = "avr"))]
119    let nrbytes = 0;
120
121    nrbytes
122}
123
124// vim: ts=4 sw=4 expandtab