Skip to content
This repository was archived by the owner on Jan 24, 2022. It is now read-only.

Commit eb9970f

Browse files
authored
Merge pull request #43 from japaric/swap-ld
Zero cost stack overflow protection, take 2
2 parents 4238831 + a535074 commit eb9970f

File tree

2 files changed

+245
-10
lines changed

2 files changed

+245
-10
lines changed

link.x

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,29 @@ SECTIONS
4848
. = ALIGN(4);
4949
} > FLASH
5050

51-
.bss : ALIGN(4)
51+
/* limits of the .stack region */
52+
_estack = _stack_start;
53+
/* HACK the `true` case indicates that two RAM regions are being used and
54+
/* that the stack was placed in the second region. In that case we don't know
55+
/* the size of the second RAM region, or its start address, so we just assume
56+
/* its zero sized */
57+
_sstack = _stack_start < ORIGIN(RAM)? _stack_start : ORIGIN(RAM);
58+
59+
/* fictitious region that represents the memory available for the stack */
60+
.stack _sstack (INFO) : ALIGN(4)
61+
{
62+
. += (_estack - _sstack);
63+
}
64+
65+
PROVIDE(_sbss = ORIGIN(RAM));
66+
.bss _sbss : ALIGN(4)
5267
{
53-
_sbss = .;
5468
*(.bss .bss.*);
5569
. = ALIGN(4);
5670
_ebss = .;
5771
} > RAM
5872

59-
.data : ALIGN(4)
73+
.data _ebss : ALIGN(4)
6074
{
6175
_sidata = LOADADDR(.data);
6276
_sdata = .;
@@ -65,6 +79,17 @@ SECTIONS
6579
_edata = .;
6680
} > RAM AT > FLASH
6781

82+
PROVIDE(_heap_size = 0);
83+
84+
_sheap = _edata;
85+
_eheap = _sheap + _heap_size;
86+
87+
/* fictitious region that represents the memory available for the heap */
88+
.heap _sheap (INFO) : ALIGN(4)
89+
{
90+
. += _heap_size;
91+
}
92+
6893
/* fake output .got section */
6994
/* Dynamic relocations are unsupported. This section is only used to detect
7095
relocatable code in the input files and raise an error if relocatable code

src/lib.rs

Lines changed: 217 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
//!
3232
//! - A `_sheap` symbol at whose address you can locate a heap.
3333
//!
34+
//! - Zero cost stack overflow protection when using the `cortex-m-rt-ld` linker.
35+
//!
3436
//! # Example
3537
//!
3638
//! Creating a new bare metal project. (I recommend you use the
@@ -41,13 +43,13 @@
4143
//! $ cargo new --bin app && cd $_
4244
//!
4345
//! $ # add this crate as a dependency
44-
//! $ edit Cargo.toml && cat $_
46+
//! $ $EDITOR Cargo.toml && tail $_
4547
//! [dependencies.cortex-m-rt]
4648
//! features = ["abort-on-panic"]
4749
//! version = "0.3.0"
4850
//!
4951
//! $ # tell Xargo which standard crates to build
50-
//! $ edit Xargo.toml && cat $_
52+
//! $ $EDITOR Xargo.toml && cat $_
5153
//! [dependencies.core]
5254
//! stage = 0
5355
//!
@@ -56,15 +58,15 @@
5658
//! stage = 1
5759
//!
5860
//! $ # memory layout of the device
59-
//! $ edit memory.x && cat $_
61+
//! $ $EDITOR memory.x && cat $_
6062
//! MEMORY
6163
//! {
6264
//! /* NOTE K = KiBi = 1024 bytes */
6365
//! FLASH : ORIGIN = 0x08000000, LENGTH = 128K
6466
//! RAM : ORIGIN = 0x20000000, LENGTH = 8K
6567
//! }
6668
//!
67-
//! $ edit src/main.rs && cat $_
69+
//! $ $EDITOR src/main.rs && cat $_
6870
//! ```
6971
//!
7072
//! ``` ignore,no_run
@@ -102,8 +104,203 @@
102104
//! 8000400: b580 push {r7, lr}
103105
//! 8000402: 466f mov r7, sp
104106
//! 8000404: b084 sub sp, #8
107+
//!
108+
//!
109+
//! $ arm-none-eabi-size -Ax $(find target -name app) | head
110+
//! target/thumbv7m-none-eabi/debug/app :
111+
//! section size addr
112+
//! .vector_table 0x400 0x8000000
113+
//! .text 0x24a 0x8000400
114+
//! .rodata 0x0 0x800064c
115+
//! .stack 0x2000 0x20000000
116+
//! .bss 0x0 0x20000000
117+
//! .data 0x0 0x20000000
118+
//! ```
119+
//!
120+
//! ## Zero cost stack overflow protection
121+
//!
122+
//! Consider the following variation of the previous program:
123+
//!
124+
//! ``` ignore
125+
//! extern crate cortex_m_rt;
126+
//!
127+
//! const N: usize = 256;
128+
//! static mut XS: [u32; N] = [0; N];
129+
//!
130+
//! fn main() {
131+
//! #[inline(never)]
132+
//! fn fib(n: u32) -> u32 {
133+
//! unsafe { assert!(XS.iter().all(|x| *x == 0)) }
134+
//!
135+
//! if n < 2 {
136+
//! 1
137+
//! } else {
138+
//! fib(n - 1) + fib(n - 2)
139+
//! }
140+
//! }
141+
//!
142+
//! let x = fib(400);
143+
//! unsafe { *XS.iter_mut().first().unwrap() = x }
144+
//! }
145+
//! ```
146+
//!
147+
//! This program allocates a 1KB array in `.bss`, recursively computes the 400th fibonacci number
148+
//! and stores the result in the head of the array. This program will hit a stack overflow at
149+
//! runtime because there's not enough memory to recursively call the `fib` function so many times.
150+
//!
151+
//! If you inspect the program using GDB you'll see that the assertion failed after `fib` was nested
152+
//! around 300 times.
153+
//!
154+
//! ``` console
155+
//! > continue
156+
//! Program received signal SIGTRAP, Trace/breakpoint trap.
157+
//!
158+
//! > backtrace
159+
//! #0 0x08000516 in cortex_m_rt::default_handler ()
160+
//! #1 <signal handler called>
161+
//! #2 0x0800050a in rust_begin_unwind ()
162+
//! #3 0x08000586 in core::panicking::panic_fmt ()
163+
//! #4 0x0800055c in core::panicking::panic ()
164+
//! #5 0x080004f6 in app::main::fib ()
165+
//! #6 0x080004a0 in app::main::fib ()
166+
//! (..)
167+
//! #301 0x080004a0 in app::main::fib ()
168+
//! #302 0x080004a0 in app::main::fib ()
169+
//! #303 0x08000472 in app::main ()
170+
//! #304 0x08000512 in cortex_m_rt::lang_items::start ()
171+
//! #305 0x08000460 in cortex_m_rt::reset_handler ()
105172
//! ```
106173
//!
174+
//! What this means is that the stack grew so much that it crashed into the `.bss` section and
175+
//! overwrote the memory in there. Continuing the GDB session you can confirm that the `XS` variable
176+
//! has been modified:
177+
//!
178+
//! ``` console
179+
//! > x/4 0x20000000 # start of .bss
180+
//! 0x20000000 <app::XS>: 0x00000000 0x00000000 0x00000000 0x00000000
181+
//!
182+
//! > x/4 0x200003f0 # end of .bss
183+
//! 0x200003f0 <app::XS+1008>: 0x20000400 0x080004f5 0x00000000 0x00000001
184+
//! ```
185+
//!
186+
//! The problem is that the stack is growing towards the `.bss` section and both sections overlap as
187+
//! shown below:
188+
//!
189+
//! ``` console
190+
//! $ arm-none-eabi-size -Ax $(find target -name app)
191+
//! section size addr
192+
//! .vector_table 0x400 0x8000000
193+
//! .text 0x186 0x8000400
194+
//! .rodata 0x50 0x8000590
195+
//! .stack 0x2000 0x20000000
196+
//! .bss 0x400 0x20000000
197+
//! .data 0x0 0x20000400
198+
//! ```
199+
//!
200+
//! Graphically the RAM sections look like this:
201+
//!
202+
//! <p align="center">
203+
//! <img alt="Stack overflow" src="https://i.imgur.com/haJKXr4.png">
204+
//! </p>
205+
//!
206+
//! To prevent memory corruption due to stack overflows in this scenario it suffices to switch the
207+
//! sections so that the `.bss` section is near the end of the RAM region and the `.stack` comes
208+
//! *before* `.bss`, at a lower address.
209+
//!
210+
//! To swap the sections you can use the [`cortex-m-rt-ld`] linker to link the program.
211+
//!
212+
//! ``` console
213+
//! $ cargo install cortex-m-rt-ld
214+
//!
215+
//! $ xargo rustc --target thumbv7m-none-eabi -- \
216+
//! -C link-arg=-Tlink.x -C linker=cortex-m-rt-ld -Z linker-flavor=ld
217+
//! ```
218+
//!
219+
//! Now you get non overlapping linker sections:
220+
//!
221+
//! ``` console
222+
//! section size addr
223+
//! .vector_table 0x400 0x8000000
224+
//! .text 0x186 0x8000400
225+
//! .rodata 0x50 0x8000590
226+
//! .stack 0x1c00 0x20000000
227+
//! .bss 0x400 0x20001c00
228+
//! .data 0x0 0x20002000
229+
//! ```
230+
//!
231+
//! Note that the `.stack` section is smaller now. Graphically, the memory layout now looks like
232+
//! this:
233+
//!
234+
//! <p align="center">
235+
//! <img alt="Swapped sections" src="https://i.imgur.com/waOKpHw.png">
236+
//! </p>
237+
//!
238+
//! On stack overflows `.stack` will hit the lower boundary of the RAM region raising a hard fault
239+
//! exception, instead of silently corrupting the `.bss` section.
240+
//!
241+
//! You can confirm this by inspecting the program in GDB.
242+
//!
243+
//! ``` console
244+
//! > continue
245+
//! Program received signal SIGTRAP, Trace/breakpoint trap.
246+
//!
247+
//! > p $sp
248+
//! $1 = (void *) 0x1ffffff0
249+
//! ```
250+
//!
251+
//! The failure mode this time was the `.stack` crashing into the RAM boundary. The variable `XS` is
252+
//! unaffected this time:
253+
//!
254+
//! ``` console
255+
//! > x/4x app::XS
256+
//! 0x20001c00 <app::XS>: 0x00000000 0x00000000 0x00000000 0x00000000
257+
//!
258+
//! > x/4x app::XS+252
259+
//! 0x20001ff0 <app::XS+1008>: 0x00000000 0x00000000 0x00000000 0x00000000
260+
//! ```
261+
//!
262+
//! ## `.heap`
263+
//!
264+
//! If your program makes use of a `.heap` section a similar problem can occur:
265+
//!
266+
//! <p align="center">
267+
//! <img alt="Memory layout when `.heap` exists" src="https://i.imgur.com/kFHRGiF.png">
268+
//! </p>
269+
//!
270+
//! The `.stack` can crash into the `.heap`, or vice versa, and you'll also get memory corruption.
271+
//!
272+
//! `cortex-m-rt-ld` can also be used in this case but the size of the `.heap` section must be
273+
//! specified via the `_heap_size` symbol in `memory.x`, or in any other linker script.
274+
//!
275+
//! ``` console
276+
//! $ $EDITOR memory.x && tail -n1 $_
277+
//! _heap_size = 0x400;
278+
//! ```
279+
//!
280+
//! ``` console
281+
//! $ xargo rustc --target thumbv7m-none-eabi -- \
282+
//! -C link-arg=-Tlink.x -C linker=cortex-m-rt-ld -Z linker-flavor=ld
283+
//!
284+
//! $ arm-none-eabi-size -Ax $(find target -name app) | head
285+
//! section size addr
286+
//! .vector_table 0x400 0x8000000
287+
//! .text 0x1a8 0x8000400
288+
//! .rodata 0x50 0x80005b0
289+
//! .stack 0x1800 0x20000000
290+
//! .bss 0x400 0x20001800
291+
//! .data 0x0 0x20001c00
292+
//! .heap 0x400 0x20001c00
293+
//! ```
294+
//!
295+
//! Graphically the memory layout looks like this:
296+
//!
297+
//! <p align="center">
298+
//! <img alt="Swapped sections when `.heap` exists" src="https://i.imgur.com/6Y5DaBp.png">
299+
//! </p>
300+
//!
301+
//! Now both stack overflows and dynamic memory over-allocations (OOM) will generate hard fault
302+
//! exceptions, instead of running into each other.
303+
//!
107304
//! # Symbol interfaces
108305
//!
109306
//! This crate makes heavy use of symbols, linker sections and linker scripts to
@@ -213,7 +410,7 @@
213410
//!
214411
//! Allocating the call stack on a different RAM region.
215412
//!
216-
//! ```,ignore
413+
//! ``` ignore
217414
//! MEMORY
218415
//! {
219416
//! /* call stack will go here */
@@ -226,6 +423,10 @@
226423
//! _stack_start = ORIGIN(CCRAM) + LENGTH(CCRAM);
227424
//! ```
228425
//!
426+
//! ### `_heap_size`
427+
//!
428+
//! The size of the `.heap` section. Only meaningful when using `cortex-m-rt-ld`.
429+
//!
229430
//! ### `_stext`
230431
//!
231432
//! This symbol indicates where the `.text` section will be located. If not
@@ -241,7 +442,7 @@
241442
//!
242443
//! Locate the `.text` section 1024 bytes after the start of the FLASH region.
243444
//!
244-
//! ```,ignore
445+
//! ``` ignore
245446
//! _stext = ORIGIN(FLASH) + 0x400;
246447
//! ```
247448
//!
@@ -253,7 +454,7 @@
253454
//!
254455
//! #### Example
255456
//!
256-
//! ```,ignore
457+
//! ``` ignore
257458
//! extern crate some_allocator;
258459
//!
259460
//! // Size of the heap in bytes
@@ -270,6 +471,15 @@
270471
//! }
271472
//! }
272473
//! ```
474+
//!
475+
//! *NOTE* if you are using `cortex-m-rt-ld` and/or have defined the `_heap_size` symbol then you should
476+
//! use the address of the `_eheap` to compute the size of the `.heap` section, instead of
477+
//! duplicating the value that you wrote in `memory.x`.
478+
//!
479+
//! [1]: https://doc.rust-lang.org/unstable-book/language-features/lang-items.html
480+
//! [qs]: https://docs.rs/cortex-m-quickstart/0.2.0/cortex_m_quickstart/
481+
//! [`cortex-m-rt-ld`]: https://crates.io/crates/cortex-m-rt-ld
482+
//! [2]: https://sourceware.org/binutils/docs/ld/MEMORY.html
273483
274484
#![cfg_attr(any(target_arch = "arm", feature = "abort-on-panic"), feature(core_intrinsics))]
275485
#![deny(missing_docs)]

0 commit comments

Comments
 (0)