Fix flip-link, make SSP configurable (#3203)

* Test flip-link

* Add todo

* Rebuild if linker files change

* Let espsegs show .stack

* Try to fix flip_link

* Changelog

* Make SSP configurable

* Substitute config values
This commit is contained in:
Dániel Buga 2025-03-05 15:09:17 +01:00 committed by GitHub
parent eb771e1652
commit f535f1c6fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 242 additions and 137 deletions

View File

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Support for `rand_core` 0.9 (#3211)
- `ESP_HAL_CONFIG_STACK_GUARD_OFFSET` and `ESP_HAL_CONFIG_STACK_GUARD_VALUE` to configure Rust's [Stack smashing protection](https://doc.rust-lang.org/rustc/exploit-mitigations.html#stack-smashing-protection) (#3203)
### Changed
@ -22,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- ESP32-S2: Fixed PSRAM initialization (#3196)
- `Uart::{with_tx, with_rx}` can now be called on the async driver as well (#3212)
- ESP32: Fixed SPI3 QSPI signals (#3201)
- ESP32-C6/H2: The `flip_link` feature should no longer crash (#3203)
### Removed

View File

@ -1,4 +1,5 @@
use std::{
collections::HashMap,
env,
error::Error,
fs::{self, File},
@ -122,6 +123,19 @@ fn main() -> Result<(), Box<dyn Error>> {
Some(Validator::Enumeration(
vec![String::from("quad"), String::from("octal")]
)),
),
// Rust's stack smashing protection configuration
(
"stack-guard-offset",
"The stack guard variable will be placed this many bytes from the stack's end.",
Value::Integer(4096),
None
),
(
"stack-guard-value",
"The value to be written to the stack guard variable.",
Value::Integer(0xDEED_BAAD),
None
)
], true);
@ -166,10 +180,21 @@ fn main() -> Result<(), Box<dyn Error>> {
} else {
// RISC-V devices:
preprocess_file(&config_symbols, "ld/riscv/asserts.x", out.join("asserts.x"))?;
preprocess_file(&config_symbols, "ld/riscv/debug.x", out.join("debug.x"))?;
preprocess_file(
&config_symbols,
&cfg,
"ld/riscv/asserts.x",
out.join("asserts.x"),
)?;
preprocess_file(
&config_symbols,
&cfg,
"ld/riscv/debug.x",
out.join("debug.x"),
)?;
preprocess_file(
&config_symbols,
&cfg,
"ld/riscv/hal-defaults.x",
out.join("hal-defaults.x"),
)?;
@ -177,8 +202,8 @@ fn main() -> Result<(), Box<dyn Error>> {
// With the architecture-specific linker scripts taken care of, we can copy all
// remaining linker scripts which are common to all devices:
copy_dir_all(&config_symbols, "ld/sections", &out)?;
copy_dir_all(&config_symbols, format!("ld/{device_name}"), &out)?;
copy_dir_all(&config_symbols, &cfg, "ld/sections", &out)?;
copy_dir_all(&config_symbols, &cfg, format!("ld/{device_name}"), &out)?;
Ok(())
}
@ -188,6 +213,7 @@ fn main() -> Result<(), Box<dyn Error>> {
fn copy_dir_all(
config_symbols: &[&str],
cfg: &HashMap<String, Value>,
src: impl AsRef<Path>,
dst: impl AsRef<Path>,
) -> std::io::Result<()> {
@ -198,12 +224,14 @@ fn copy_dir_all(
if ty.is_dir() {
copy_dir_all(
config_symbols,
cfg,
entry.path(),
dst.as_ref().join(entry.file_name()),
)?;
} else {
preprocess_file(
config_symbols,
cfg,
entry.path(),
dst.as_ref().join(entry.file_name()),
)?;
@ -215,9 +243,12 @@ fn copy_dir_all(
/// A naive pre-processor for linker scripts
fn preprocess_file(
config: &[&str],
cfg: &HashMap<String, Value>,
src: impl AsRef<Path>,
dst: impl AsRef<Path>,
) -> std::io::Result<()> {
println!("cargo:rerun-if-changed={}", src.as_ref().display());
let file = File::open(src)?;
let mut out_file = File::create(dst)?;
@ -225,7 +256,7 @@ fn preprocess_file(
take.push(true);
for line in std::io::BufReader::new(file).lines() {
let line = line?;
let line = substitute_config(cfg, &line?);
let trimmed = line.trim();
if let Some(condition) = trimmed.strip_prefix("#IF ") {
@ -252,6 +283,43 @@ fn preprocess_file(
Ok(())
}
fn substitute_config(cfg: &HashMap<String, Value>, line: &str) -> String {
let mut result = String::new();
let mut chars = line.chars().peekable();
while let Some(c) = chars.next() {
if c != '$' {
result.push(c);
continue;
}
let Some('{') = chars.peek() else {
result.push(c);
continue;
};
chars.next();
let mut key = String::new();
for c in chars.by_ref() {
if c == '}' {
break;
}
key.push(c);
}
match cfg
.get(&key)
.unwrap_or_else(|| panic!("missing config key: {key}"))
{
Value::Bool(true) => result.push('1'),
Value::Bool(false) => result.push('0'),
Value::Integer(value) => result.push_str(&value.to_string()),
Value::String(value) => result.push_str(value),
}
}
result
}
#[cfg(feature = "esp32")]
fn generate_memory_extras() -> Vec<u8> {
let reserve_dram = if cfg!(feature = "bluetooth") {

View File

@ -19,9 +19,11 @@ INCLUDE "fixups/rtc_fast_rwdata_dummy.x"
/* END ESP32 fixups */
/* Shared sections - ordering matters */
INCLUDE "rwtext.x"
SECTIONS {
INCLUDE "rwtext.x"
INCLUDE "rwdata.x"
}
INCLUDE "text.x"
INCLUDE "rwdata.x"
INCLUDE "rodata.x"
INCLUDE "rtc_fast.x"
INCLUDE "rtc_slow.x"

View File

@ -80,9 +80,11 @@ PROVIDE(__global_pointer$ = _data_start + 0x800);
/* end of esp32c2 fixups */
/* Shared sections - ordering matters */
INCLUDE "rwtext.x"
SECTIONS {
INCLUDE "rwtext.x"
INCLUDE "rwdata.x"
}
INCLUDE "text.x"
INCLUDE "rwdata.x"
INCLUDE "rodata.x"
INCLUDE "stack.x"
INCLUDE "dram2.x"

View File

@ -80,9 +80,11 @@ PROVIDE(__global_pointer$ = _data_start + 0x800);
/* end of esp32c3 fixups */
/* Shared sections - ordering matters */
INCLUDE "rwtext.x"
SECTIONS {
INCLUDE "rwtext.x"
INCLUDE "rwdata.x"
}
INCLUDE "text.x"
INCLUDE "rwdata.x"
INCLUDE "rodata.x"
INCLUDE "rtc_fast.x"
INCLUDE "stack.x"

View File

@ -55,8 +55,17 @@ SECTIONS {
KEEP(*(.trap));
*(.trap.*);
} > RWTEXT
/* Shared sections - ordering matters */
INCLUDE "rwtext.x"
INCLUDE "rwdata.x"
/* End of Shared sections */
}
INSERT BEFORE .rwtext;
#IF ESP_HAL_CONFIG_FLIP_LINK
/* INSERT BEFORE does not seem to work for the .stack section. Instead, we place every RAM
section after .stack if `flip_link` is enabled. */
INSERT AFTER .stack;
#ENDIF
SECTIONS {
/**
@ -71,15 +80,13 @@ SECTIONS {
INSERT BEFORE .rodata;
/* end of esp32c6 fixups */
/* Shared sections - ordering matters */
INCLUDE "rwtext.x"
/* Shared sections #2 - ordering matters */
INCLUDE "text.x"
INCLUDE "rwdata.x"
INCLUDE "rodata.x"
INCLUDE "rtc_fast.x"
INCLUDE "stack.x"
INCLUDE "dram2.x"
/* End of Shared sections */
/* End of Shared sections #2 */
INCLUDE "debug.x"

View File

@ -43,14 +43,24 @@ PROVIDE(_start_trap = default_start_trap);
/* Must be called __global_pointer$ for linker relaxations to work. */
PROVIDE(__global_pointer$ = _data_start + 0x800);
SECTIONS {
.trap : ALIGN(4)
{
KEEP(*(.trap));
*(.trap.*);
} > RWTEXT
/* Shared sections - ordering matters */
INCLUDE "rwtext.x"
INCLUDE "rwdata.x"
/* End of Shared sections */
}
INSERT BEFORE .rwtext;
#IF ESP_HAL_CONFIG_FLIP_LINK
/* INSERT BEFORE does not seem to work for the .stack section. Instead, we place every RAM
section after .stack if `flip_link` is enabled. */
INSERT AFTER .stack;
#ENDIF
SECTIONS {
/**
@ -64,15 +74,13 @@ SECTIONS {
}
INSERT BEFORE .rodata;
/* Shared sections - ordering matters */
INCLUDE "rwtext.x"
/* Shared sections #2 - ordering matters */
INCLUDE "text.x"
INCLUDE "rwdata.x"
INCLUDE "rodata.x"
INCLUDE "rtc_fast.x"
INCLUDE "stack.x"
INCLUDE "dram2.x"
/* End of Shared sections */
/* End of Shared sections #2 */
INCLUDE "debug.x"

View File

@ -27,9 +27,11 @@ INCLUDE "fixups/rtc_fast_rwdata_dummy.x"
/* End of fixups for esp32s2 */
/* Shared sections - ordering matters */
INCLUDE "rwtext.x"
SECTIONS {
INCLUDE "rwtext.x"
INCLUDE "rwdata.x"
}
INCLUDE "text.x"
INCLUDE "rwdata.x"
INCLUDE "rodata.x"
INCLUDE "rtc_fast.x"
INCLUDE "rtc_slow.x"

View File

@ -41,9 +41,11 @@ INCLUDE "fixups/rodata_dummy.x"
/* End of ESP32S3 fixups */
/* Shared sections - ordering matters */
INCLUDE "rwtext.x"
SECTIONS {
INCLUDE "rwtext.x"
INCLUDE "rwdata.x"
}
INCLUDE "text.x"
INCLUDE "rwdata.x"
INCLUDE "rodata.x"
INCLUDE "rtc_fast.x"
INCLUDE "rtc_slow.x"

View File

@ -1,63 +1,59 @@
.data : ALIGN(4)
{
_data_start = ABSOLUTE(.);
. = ALIGN (4);
#IF ESP_HAL_CONFIG_PLACE_SWITCH_TABLES_IN_RAM
*(.rodata.*_esp_hal_internal_handler*)
*(.rodata..Lswitch.table.*)
*(.rodata.cst*)
#ENDIF
SECTIONS {
.data : ALIGN(4)
{
_data_start = ABSOLUTE(.);
. = ALIGN (4);
#IF ESP_HAL_CONFIG_PLACE_ANON_IN_RAM
*(.rodata..Lanon .rodata..Lanon.*)
#ENDIF
#IF ESP_HAL_CONFIG_PLACE_SWITCH_TABLES_IN_RAM
*(.rodata.*_esp_hal_internal_handler*)
*(.rodata..Lswitch.table.*)
*(.rodata.cst*)
#ENDIF
*(.sdata .sdata.* .sdata2 .sdata2.*);
*(.data .data.*);
*(.data1)
_data_end = ABSOLUTE(.);
. = ALIGN(4);
} > RWDATA AT > RODATA
#IF ESP_HAL_CONFIG_PLACE_ANON_IN_RAM
*(.rodata..Lanon .rodata..Lanon.*)
#ENDIF
/* LMA of .data */
_sidata = LOADADDR(.data);
*(.sdata .sdata.* .sdata2 .sdata2.*);
*(.data .data.*);
*(.data1)
_data_end = ABSOLUTE(.);
. = ALIGN(4);
} > RWDATA AT > RODATA
.bss (NOLOAD) : ALIGN(4)
{
_bss_start = ABSOLUTE(.);
. = ALIGN (4);
*(.dynsbss)
*(.sbss)
*(.sbss.*)
*(.gnu.linkonce.sb.*)
*(.scommon)
*(.sbss2)
*(.sbss2.*)
*(.gnu.linkonce.sb2.*)
*(.dynbss)
*(.sbss .sbss.* .bss .bss.*);
*(.share.mem)
*(.gnu.linkonce.b.*)
*(COMMON)
_bss_end = ABSOLUTE(.);
. = ALIGN(4);
} > RWDATA
/* LMA of .data */
_sidata = LOADADDR(.data);
.noinit (NOLOAD) : ALIGN(4)
{
. = ALIGN(4);
*(.noinit .noinit.*)
. = ALIGN(4);
} > RWDATA
.bss (NOLOAD) : ALIGN(4)
{
_bss_start = ABSOLUTE(.);
. = ALIGN (4);
*(.dynsbss)
*(.sbss)
*(.sbss.*)
*(.gnu.linkonce.sb.*)
*(.scommon)
*(.sbss2)
*(.sbss2.*)
*(.gnu.linkonce.sb2.*)
*(.dynbss)
*(.sbss .sbss.* .bss .bss.*);
*(.share.mem)
*(.gnu.linkonce.b.*)
*(COMMON)
_bss_end = ABSOLUTE(.);
. = ALIGN(4);
} > RWDATA
.noinit (NOLOAD) : ALIGN(4)
{
. = ALIGN(4);
*(.noinit .noinit.*)
. = ALIGN(4);
} > RWDATA
.data.wifi :
{
. = ALIGN(4);
*( .dram1 .dram1.*)
. = ALIGN(4);
} > RWDATA AT > RODATA
}
.data.wifi :
{
. = ALIGN(4);
*( .dram1 .dram1.*)
. = ALIGN(4);
} > RWDATA AT > RODATA

View File

@ -1,22 +1,20 @@
SECTIONS {
.rwtext : ALIGN(4)
{
. = ALIGN (4);
*(.rwtext.literal .rwtext .rwtext.literal.* .rwtext.*)
. = ALIGN(4);
} > RWTEXT
.rwtext : ALIGN(4)
{
. = ALIGN (4);
*(.rwtext.literal .rwtext .rwtext.literal.* .rwtext.*)
. = ALIGN(4);
} > RWTEXT
.rwtext.wifi :
{
. = ALIGN(4);
*( .wifi0iram .wifi0iram.*)
*( .wifirxiram .wifirxiram.*)
*( .wifislprxiram .wifislprxiram.*)
*( .wifislpiram .wifislpiram.*)
*( .phyiram .phyiram.*)
*( .iram1 .iram1.*)
*( .wifiextrairam.* )
*( .coexiram.* )
. = ALIGN(4);
} > RWTEXT AT > RODATA
}
.rwtext.wifi :
{
. = ALIGN(4);
*( .wifi0iram .wifi0iram.*)
*( .wifirxiram .wifirxiram.*)
*( .wifislprxiram .wifislprxiram.*)
*( .wifislpiram .wifislpiram.*)
*( .phyiram .phyiram.*)
*( .iram1 .iram1.*)
*( .wifiextrairam.* )
*( .coexiram.* )
. = ALIGN(4);
} > RWTEXT AT > RODATA

View File

@ -1,5 +1,3 @@
#IF ESP_HAL_CONFIG_FLIP_LINK
/* no Xtensa chip is supported - so we can assume RISC-V */
SECTIONS {
/* must be last segment using RWDATA */
.stack (NOLOAD) : ALIGN(4)
@ -7,37 +5,19 @@ SECTIONS {
_stack_end = ABSOLUTE(.);
_stack_end_cpu0 = ABSOLUTE(.);
/* The stack_guard for `stack-protector` mitigation - https://doc.rust-lang.org/rustc/exploit-mitigations.html#stack-smashing-protection */
__stack_chk_guard = _stack_end + ${ESP_HAL_CONFIG_STACK_GUARD_OFFSET};
/* no Xtensa chip is supported - so we can assume RISC-V */
#IF ESP_HAL_CONFIG_FLIP_LINK
/* Since we cannot know how much the alignment padding of the sections will add we shrink the stack for "the worst case"
*/
. = . + LENGTH(RWDATA) - (SIZEOF(.trap) + SIZEOF(.rwtext) + SIZEOF(.rwtext.wifi) + SIZEOF(.data) + SIZEOF(.bss) + SIZEOF(.noinit) + SIZEOF(.data.wifi)) - 304;
#ELSE
. = ORIGIN(RWDATA) + LENGTH(RWDATA);
#ENDIF
. = ALIGN (4);
_stack_start = ABSOLUTE(.);
_stack_start_cpu0 = ABSOLUTE(.);
} > RWDATA
}
INSERT BEFORE .trap;
#ELSE
SECTIONS {
/* must be last segment using RWDATA */
.stack (NOLOAD) : ALIGN(4)
{
. = ALIGN (4);
_stack_end = ABSOLUTE(.);
_stack_end_cpu0 = ABSOLUTE(.);
} > RWDATA
}
PROVIDE(_stack_start = ORIGIN(RWDATA) + LENGTH(RWDATA));
PROVIDE(_stack_start_cpu0 = ORIGIN(RWDATA) + LENGTH(RWDATA));
#ENDIF
/*
Provide the stack_guard for `stack-protector`
Ideally the offset should be configurable - should be done once we have https://github.com/esp-rs/esp-hal/issues/1111
*/
PROVIDE(__stack_chk_guard = _stack_end + 4096);

View File

@ -418,7 +418,10 @@ fn hal_main(a0: usize, a1: usize, a2: usize) -> ! {
let stack_chk_guard = core::ptr::addr_of_mut!(__stack_chk_guard);
// we _should_ use a random value but we don't have a good source for random
// numbers here
stack_chk_guard.write_volatile(0xdeadbabe);
stack_chk_guard.write_volatile(esp_config::esp_config_int!(
u32,
"ESP_HAL_CONFIG_STACK_GUARD_VALUE"
));
main(a0, a1, a2);
}

View File

@ -114,7 +114,10 @@ pub unsafe extern "C" fn ESP32Reset() -> ! {
let stack_chk_guard = core::ptr::addr_of_mut!(__stack_chk_guard);
// we _should_ use a random value but we don't have a good source for random
// numbers here
stack_chk_guard.write_volatile(0xdeadbabe);
stack_chk_guard.write_volatile(esp_config::esp_config_int!(
u32,
"ESP_HAL_CONFIG_STACK_GUARD_VALUE"
));
}
crate::interrupt::setup_interrupts();

View File

@ -118,7 +118,10 @@ pub unsafe extern "C" fn ESP32Reset() -> ! {
let stack_chk_guard = core::ptr::addr_of_mut!(__stack_chk_guard);
// we _should_ use a random value but we don't have a good source for random
// numbers here
stack_chk_guard.write_volatile(0xdeadbabe);
stack_chk_guard.write_volatile(esp_config::esp_config_int!(
u32,
"ESP_HAL_CONFIG_STACK_GUARD_VALUE"
));
}
crate::interrupt::setup_interrupts();

View File

@ -157,7 +157,10 @@ pub unsafe extern "C" fn ESP32Reset() -> ! {
let stack_chk_guard = core::ptr::addr_of_mut!(__stack_chk_guard);
// we _should_ use a random value but we don't have a good source for random
// numbers here
stack_chk_guard.write_volatile(0xdeadbabe);
stack_chk_guard.write_volatile(esp_config::esp_config_int!(
u32,
"ESP_HAL_CONFIG_STACK_GUARD_VALUE"
));
}
crate::interrupt::setup_interrupts();

View File

@ -1,9 +1,9 @@
[target.'cfg(target_arch = "riscv32")']
runner = "probe-rs run --preverify"
rustflags = [
"-C", "link-arg=-Tlinkall.x",
"-C", "link-arg=-Tembedded-test.x",
"-C", "link-arg=-Tdefmt.x",
"-C", "link-arg=-Tlinkall.x",
"-C", "force-frame-pointers"
]
@ -11,9 +11,9 @@ rustflags = [
runner = "probe-rs run --preverify"
rustflags = [
"-C", "link-arg=-nostartfiles",
"-C", "link-arg=-Wl,-Tlinkall.x",
"-C", "link-arg=-Tdefmt.x",
"-C", "link-arg=-Tembedded-test.x",
"-C", "link-arg=-Tdefmt.x",
"-C", "link-arg=-Wl,-Tlinkall.x",
]
[env]

View File

@ -56,6 +56,10 @@ harness = false
name = "get_time"
harness = false
[[test]]
name = "flip_link"
harness = false
[[test]]
name = "gpio"
harness = false

View File

@ -0,0 +1,20 @@
//! Tests flip_link
//% CHIPS: esp32c6 esp32h2
//% ENV: ESP_HAL_CONFIG_FLIP_LINK = true
#![no_std]
#![no_main]
use hil_test as _;
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
#[test]
fn test() {
let _p = esp_hal::init(Default::default());
defmt::info!("Hello, world!");
defmt::assert_eq!(1 + 1, 2);
}
}