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