What changed, and why it matters
This is a build-system refactoring commit for Trezor firmware. It moves feature-resolution code into a new module and shifts hard-coded ELF section lists and signing-tool choices into TOML configuration files. There is no direct evidence of a security vulnerability being fixed; it is a code-cleanup and maintainability change.
No immediate security action required. Treat as normal build-system refactoring. If reviewing for supply-chain security, verify that the TOML config files correctly preserve the previous section ordering and signing-tool assignments, and that the new `feature_resolver.rs` module does not introduce unintended feature combinations.
Security signals we found
No security-relevant keywords in commit title or message
No changelog entry requested
Refactoring only: code moved, not removed or added in a security-critical way
Signing tool selection logic changed from hard-coded Rust match to TOML config, but same tools selected for same models
ELF section extraction logic changed from hard-coded to config-driven, but same sections specified for each component
Evidence from the diff
The commit refactors the Rust xtask build tooling. Key changes: (1) extracts resolve_features and configure_cargo from args.rs into a new feature_resolver.rs module; (2) moves ELF section extraction lists, STM32F4 split-flash parameters, and secmon prodtest body/header section lists from hard-coded match statements in postbuild.rs into each component’s target.toml; (3) replaces the hard-coded header_tool_name function with a bootloader_header_tool field in model.toml, defaulting to headertool; (4) adds -Zemit-stack-sizes when emit_memory_analysis is enabled. The functional behavior appears preserved, just data-driven.
Changed components
core/embed/xtask build toolingcore/embed/projects/*/target.tomlcore/embed/models/D002/model.tomlcore/embed/models/T3W1/model.tomlInspect captured patch +361 / −340
diff --git a/core/embed/models/D002/model.toml b/core/embed/models/D002/model.toml
index 22b0f98e..225e6d39 100644
--- a/core/embed/models/D002/model.toml
+++ b/core/embed/models/D002/model.toml
@@ -1,6 +1,7 @@
mcu = "stm32u5g"
default_board = "dk"
secmon = true
+bootloader_header_tool = "headertool_pq"
features = [
"boot_ucb",
diff --git a/core/embed/models/T3W1/model.toml b/core/embed/models/T3W1/model.toml
index a78c2a4a..d6493162 100644
--- a/core/embed/models/T3W1/model.toml
+++ b/core/embed/models/T3W1/model.toml
@@ -2,6 +2,7 @@ mcu = "stm32u5g"
default_board = "revC"
emulator_board = "unix"
secmon = true
+bootloader_header_tool = "headertool_pq"
features = [
"backup_ram",
diff --git a/core/embed/projects/boardloader/target.toml b/core/embed/projects/boardloader/target.toml
index 768e3598..0dff862d 100644
--- a/core/embed/projects/boardloader/target.toml
+++ b/core/embed/projects/boardloader/target.toml
@@ -14,3 +14,5 @@ uses = [
"secure_aes",
"tamper",
]
+
+elf_sections = [".vector_table", ".text", ".data", ".rodata", ".capabilities"]
diff --git a/core/embed/projects/bootloader/target.toml b/core/embed/projects/bootloader/target.toml
index dfdb7a19..b4828fba 100644
--- a/core/embed/projects/bootloader/target.toml
+++ b/core/embed/projects/bootloader/target.toml
@@ -37,3 +37,5 @@ uses = [
"touch_wakeup",
"ui_empty_lock",
]
+
+elf_sections = [".header", ".flash", ".data"]
diff --git a/core/embed/projects/bootloader_ci/target.toml b/core/embed/projects/bootloader_ci/target.toml
index 9f52122f..15212f59 100644
--- a/core/embed/projects/bootloader_ci/target.toml
+++ b/core/embed/projects/bootloader_ci/target.toml
@@ -21,3 +21,5 @@ uses = [
"telemetry",
"touch",
]
+
+elf_sections = [".header", ".flash", ".data"]
diff --git a/core/embed/projects/firmware/target.toml b/core/embed/projects/firmware/target.toml
index a3df3428..0136a817 100644
--- a/core/embed/projects/firmware/target.toml
+++ b/core/embed/projects/firmware/target.toml
@@ -42,3 +42,9 @@ uses = [
"eos",
"nem",
]
+
+elf_sections = [".vendorheader", ".header", ".flash", ".data"]
+
+# STM32F4: firmware spans two non-contiguous flash banks (bank2 starts at 0x08100000)
+split_pad_to = "0x08100000"
+split_part2_sections = [".flash2"]
diff --git a/core/embed/projects/kernel/target.toml b/core/embed/projects/kernel/target.toml
index 57bba9ca..8416d5e1 100644
--- a/core/embed/projects/kernel/target.toml
+++ b/core/embed/projects/kernel/target.toml
@@ -35,3 +35,5 @@ uses = [
"touch_wakeup",
"tropic",
]
+
+elf_sections = [".flash", ".data"]
diff --git a/core/embed/projects/prodtest/target.toml b/core/embed/projects/prodtest/target.toml
index 5f313bcb..e6368070 100644
--- a/core/embed/projects/prodtest/target.toml
+++ b/core/embed/projects/prodtest/target.toml
@@ -42,3 +42,8 @@ uses = [
"touch_wakeup",
"tropic",
]
+
+elf_sections = [".vendorheader", ".header", ".flash", ".data"]
+
+secmon_body_sections = [".secmon_header", ".flash", ".data"]
+secmon_header_sections = [".vendorheader", ".header"]
diff --git a/core/embed/projects/secmon/target.toml b/core/embed/projects/secmon/target.toml
index 989e53c9..b3f84e15 100644
--- a/core/embed/projects/secmon/target.toml
+++ b/core/embed/projects/secmon/target.toml
@@ -15,3 +15,5 @@ uses = [
"telemetry",
"tropic",
]
+
+elf_sections = [".secmon_header", ".flash", ".data", ".gnu.sgstubs"]
diff --git a/core/embed/xtask/src/args.rs b/core/embed/xtask/src/args.rs
index 3b15a28a..177f2a91 100644
--- a/core/embed/xtask/src/args.rs
+++ b/core/embed/xtask/src/args.rs
@@ -1,4 +1,4 @@
-use anyhow::{Result, anyhow, bail};
+use anyhow::{Result, anyhow};
use clap::{Args, Parser, Subcommand, ValueEnum};
use std::process;
@@ -285,261 +285,12 @@ impl BuildArgs {
}
}
- /// Resolves cargo features and target triple from the provided cli arguments.
pub fn resolve_features(&self) -> Result<ResolvedBuild> {
- let mut features: Vec<String> = vec![self.model.feature_name()];
-
- if self.emulator {
- features.push("emulator".into());
- }
-
- if self.production {
- features.push("production".into());
- }
-
- if self.bootloader_devel {
- features.push("bootloader_devel".into());
- }
-
- // if self.production && self.bootloader_devel {
- // bail!("bootloader-devel cannot be enabled in production builds");
- // }
-
- if self.force_bootloader_upgrade {
- features.push("force_bootloader_upgrade".into());
- }
-
- if self.emulator {
- features.push("dbg_console".into());
-
- if self.asan {
- features.push("asan".into());
- }
- } else {
- match (self.component, self.dbg_console) {
- (Component::Firmware, Some(_)) => features.push("dbg_console".into()),
- (Component::Secmon, Some(ConsoleType::Vcp)) => (),
- (Component::Boardloader, Some(ConsoleType::Vcp)) => (),
- (Component::Prodtest, Some(ConsoleType::Vcp)) => (),
- (_, Some(ConsoleType::Vcp)) => features.push("dbg_console_vcp".into()),
- (_, Some(ConsoleType::Swo)) => features.push("dbg_console_swo".into()),
- (_, Some(ConsoleType::SystemView)) => features.push("dbg_console_sysview".into()),
- (_, None) => (),
- }
- }
-
- let pyopt = self.pyopt.unwrap_or(true);
-
- if self.component == Component::Firmware {
- if pyopt {
- features.push("pyopt".into());
- } else {
- features.push("debug".into());
- }
-
- if self.frozen || !self.emulator {
- features.push("frozen");
- }
-
- if self.source_lines.unwrap_or(self.emulator) {
- features.push("micropy_enable_source_lines".into());
- }
-
- if self.benchmark {
- features.push("benchmark".into());
- }
-
- if self.log_stack_usage {
- features.push("log_stack_usage".into());
- }
-
- if self.block_on_vcp {
- features.push("block_on_vcp".into());
- }
-
- if self.apps {
- features.push("app_loading".into());
- }
-
- if self.mem_perf {
- features.push("memperf".into());
- }
-
- if !self.production {
- features.push("dev_keys".into());
- }
-
- if self.n4w1 {
- features.push("n4w1".into());
- }
- }
-
- if matches!(
- self.component,
- Component::Secmon | Component::Kernel | Component::Firmware
- ) {
- if !self.btc_only {
- features.push("universal_fw".into());
- }
-
- if !pyopt {
- features.push("optiga_testing".into());
- }
-
- if self.unsafe_fw {
- features.push("unsafe_fw".into());
- }
-
- if self.storage_insecure_testing_mode {
- if self.production {
- bail!("storage_insecure_testing_mode cannot be enabled in production builds");
- }
- features.push("storage_insecure_testing_mode".into());
- }
- }
-
- if matches!(
- self.component,
- Component::Firmware | Component::Bootloader | Component::Prodtest
- ) {
- if self.perf_overlay {
- features.push("ui_performance_overlay".into());
- }
-
- if self.debug_link.unwrap_or(!pyopt) {
- features.push("debuglink".into());
- features.push("ui_debug".into());
- }
-
- if self.disable_animation {
- features.push("disable_animation".into());
- }
- }
-
- if matches!(self.component, Component::Kernel) && self.debug_link.unwrap_or(!pyopt) {
- features.push("debuglink".into());
- }
-
- if matches!(
- self.component,
- Component::Firmware | Component::Kernel | Component::Secmon | Component::Prodtest
- ) {
- if self.model.has_optiga() && !self.disable_optiga {
- features.push("optiga".into());
- }
-
- if self.model.has_tropic() && !self.disable_tropic.unwrap_or(self.emulator) {
- features.push("tropic".into());
- }
- }
-
- // By default, we want to build with `frozen` for hardware targets
- if self
- .frozen
- .unwrap_or(self.component.frozen_default(self.emulator))
- {
- features.push("frozen".into());
- }
-
- // Board and model-intrinsic features from TOML config
- let model_config = config::ModelConfig::load(self.model.model_id())?;
- let board_id = if self.emulator {
- model_config
- .emulator_board
- .as_deref()
- .ok_or_else(|| anyhow!("Model {} has no emulator board", self.model.model_id()))?
- .to_string()
- } else {
- self.board
- .clone()
- .unwrap_or_else(|| model_config.default_board.clone())
- };
- let mut board_feat = config::resolve_board_features(
- self.model.model_id(),
- &model_config,
- &board_id,
- self.component,
- )?
- .features;
- if self.disable_optiga {
- board_feat.retain(|f| f != "optiga");
- }
- if self.disable_tropic.unwrap_or(self.emulator) {
- board_feat.retain(|f| f != "tropic");
- }
- features.extend(board_feat);
-
- let target_triple = if self.emulator {
- None
- } else {
- Some(model_config.target_triple()?)
- };
-
- Ok(ResolvedBuild {
- features,
- target_triple,
- })
+ crate::feature_resolver::resolve_features(self)
}
- // Configures the cargo command with the appropriate arguments and features
- // based on the provided cli arguments
pub fn configure_cargo(&self, cmd: &mut process::Command) -> Result<()> {
- let resolved = self.resolve_features()?;
- let mut rebuild_std = false;
-
- cmd.args(["--package", self.component.package_name(self.emulator)]);
- cmd.args(["--features", &resolved.features.join(",")]);
- cmd.args(["--profile", self.profile_name()]);
-
- if self.profile_name() == "release" {
- // Required by panic-immediate-abort in the release profile
- rebuild_std = true;
- }
-
- if let Some(triple) = resolved.target_triple {
- cmd.args(["--target", triple]);
- }
-
- if self.emit_memory_analysis {
- // See https://nnethercote.github.io/perf-book/type-sizes.html#measuring-type-sizes for more details
- //
- // Use --config instead of RUSTFLAGS env so that rustflags in .cargo/config.toml are
- // not overridden (RUSTFLAGS env has higher precedence and replaces them entirely).
- cmd.args(["--config", "build.rustflags=[\"-Zprint-type-sizes\"]"]);
- }
-
- if self.emulator && self.asan {
- // -Zsanitizer=address is a rustc flag passed via RUSTFLAGS.
- //
- // Without an explicit --target, cargo compiles proc-macros and the firmware in the
- // same pass and RUSTFLAGS leaks into proc-macro crates, causing "can't find crate"
- // errors. Passing --target explicitly (even the same triple as the host) makes cargo
- // separate the host (proc-macros / build scripts) and target (firmware) compilation
- // units, so RUSTFLAGS only reaches the firmware crates.
- //
- cmd.args(["--target", &helpers::host_triple()?]);
- cmd.args([
- "--config",
- "build.rustflags=[\"-Zsanitizer=address\", \"-Clink-arg=-lgcc_s\"]",
- ]);
-
- // Rebuild standard library to be compiled with sanitizer instrumentation
- rebuild_std = true;
- }
-
- if self.timings {
- cmd.arg("--timings");
- }
-
- if self.verbose {
- cmd.arg("--verbose");
- }
-
- if rebuild_std {
- cmd.arg("-Zbuild-std=core");
- }
-
- Ok(())
+ crate::feature_resolver::configure_cargo(self, cmd)
}
}
diff --git a/core/embed/xtask/src/cargo.rs b/core/embed/xtask/src/cargo.rs
index b41375f5..b6a1d90c 100644
--- a/core/embed/xtask/src/cargo.rs
+++ b/core/embed/xtask/src/cargo.rs
@@ -4,7 +4,7 @@ use std::process;
use crate::{
args::{BuildArgs, Component, TestArgs},
- artifacts, helpers, memusage, postbuild, prebuild,
+ artifacts, config, helpers, memusage, postbuild, prebuild,
};
pub fn build(args: BuildArgs) -> Result<()> {
@@ -116,13 +116,15 @@ fn build_impl(args: BuildArgs, is_dependency: bool) -> Result<()> {
if !args.emulator {
let use_dev_keys = args.bootloader_devel || !args.production;
+ let model_config = config::ModelConfig::load(args.model.model_id())?;
+
// For hardware targets, we need to convert the ELF file into a raw
// binary before signing it.
- let bin = postbuild::elf_to_bin(&elf, args.component, args.model, use_dev_keys)?;
+ let bin = postbuild::elf_to_bin(&elf, args.component, &model_config, use_dev_keys)?;
// Sign the binary except for those that don't have headers
if !matches!(args.component, Component::Boardloader | Component::Kernel) {
- postbuild::sign_binary(&bin, args.component, args.model, use_dev_keys)?;
+ postbuild::sign_binary(&bin, args.component, &model_config, use_dev_keys)?;
}
if args.component == Component::Firmware {
diff --git a/core/embed/xtask/src/config.rs b/core/embed/xtask/src/config.rs
index 86e0cf2f..7a8582cb 100644
--- a/core/embed/xtask/src/config.rs
+++ b/core/embed/xtask/src/config.rs
@@ -19,6 +19,9 @@ pub struct ModelConfig {
pub secmon: bool,
#[serde(default)]
pub targets: HashMap<String, ModelTargetOverride>,
+ /// Signing tool for bootloader/bootloader_ci. Defaults to "headertool".
+ #[serde(default)]
+ pub bootloader_header_tool: Option<String>,
}
impl ModelConfig {
@@ -35,6 +38,10 @@ impl ModelConfig {
Ok(config)
}
+ pub fn is_stm32f4(&self) -> bool {
+ matches!(self.mcu.as_str(), "stm32f427" | "stm32f429")
+ }
+
pub fn mcu_feature(&self) -> String {
format!("mcu_{}", self.mcu)
}
@@ -121,6 +128,19 @@ impl BoardConfig {
#[derive(Deserialize)]
pub struct TargetProfile {
pub uses: Vec<String>,
+ pub elf_sections: Vec<String>,
+ /// Body sections used when the model has secmon and the binary needs a
+ /// separately-signed body concatenated with a plain header.
+ #[serde(default)]
+ pub secmon_body_sections: Option<Vec<String>>,
+ #[serde(default)]
+ pub secmon_header_sections: Option<Vec<String>>,
+ /// STM32F4 only: pad address and second-bank sections for split firmware.
+ /// Part1 reuses `elf_sections`; only the bank2 extension is F4-specific.
+ #[serde(default)]
+ pub split_pad_to: Option<String>,
+ #[serde(default)]
+ pub split_part2_sections: Option<Vec<String>>,
}
impl TargetProfile {
diff --git a/core/embed/xtask/src/feature_resolver.rs b/core/embed/xtask/src/feature_resolver.rs
new file mode 100644
index 00000000..77d2843e
--- /dev/null
+++ b/core/embed/xtask/src/feature_resolver.rs
@@ -0,0 +1,246 @@
+use anyhow::{Result, anyhow, bail};
+use std::process;
+
+use crate::{
+ args::{BuildArgs, Component, ConsoleType, ResolvedBuild},
+ config, helpers,
+};
+
+/// Resolves cargo features and target triple from the provided CLI arguments.
+pub fn resolve_features(args: &BuildArgs) -> Result<ResolvedBuild> {
+ let mut features: Vec<String> = vec![args.model.feature_name()];
+
+ if args.emulator {
+ features.push("emulator".into());
+ }
+
+ if args.production {
+ features.push("production".into());
+ }
+
+ if args.bootloader_devel {
+ features.push("bootloader_devel".into());
+ }
+
+ if args.force_bootloader_upgrade {
+ features.push("force_bootloader_upgrade".into());
+ }
+
+ if args.emulator {
+ features.push("dbg_console".into());
+
+ if args.asan {
+ features.push("asan".into());
+ }
+ } else {
+ match (args.component, args.dbg_console) {
+ (Component::Firmware, Some(_)) => features.push("dbg_console".into()),
+ (Component::Secmon, Some(ConsoleType::Vcp)) => (),
+ (Component::Boardloader, Some(ConsoleType::Vcp)) => (),
+ (Component::Prodtest, Some(ConsoleType::Vcp)) => (),
+ (_, Some(ConsoleType::Vcp)) => features.push("dbg_console_vcp".into()),
+ (_, Some(ConsoleType::Swo)) => features.push("dbg_console_swo".into()),
+ (_, Some(ConsoleType::SystemView)) => features.push("dbg_console_sysview".into()),
+ (_, None) => (),
+ }
+ }
+
+ let pyopt = args.pyopt.unwrap_or(true);
+
+ if args.component == Component::Firmware {
+ if pyopt {
+ features.push("pyopt".into());
+ } else {
+ features.push("debug".into());
+ }
+
+ if args.source_lines.unwrap_or(args.emulator) {
+ features.push("micropy_enable_source_lines".into());
+ }
+
+ if args.benchmark {
+ features.push("benchmark".into());
+ }
+
+ if args.log_stack_usage {
+ features.push("log_stack_usage".into());
+ }
+
+ if args.block_on_vcp {
+ features.push("block_on_vcp".into());
+ }
+
+ if args.apps {
+ features.push("app_loading".into());
+ }
+
+ if args.mem_perf {
+ features.push("memperf".into());
+ }
+
+ if !args.production {
+ features.push("dev_keys".into());
+ }
+
+ if args.n4w1 {
+ features.push("n4w1".into());
+ }
+ }
+
+ if matches!(
+ args.component,
+ Component::Secmon | Component::Kernel | Component::Firmware
+ ) {
+ if !args.btc_only {
+ features.push("universal_fw".into());
+ }
+
+ if !pyopt {
+ features.push("optiga_testing".into());
+ }
+
+ if args.unsafe_fw {
+ features.push("unsafe_fw".into());
+ }
+
+ if args.storage_insecure_testing_mode {
+ if args.production {
+ bail!("storage_insecure_testing_mode cannot be enabled in production builds");
+ }
+ features.push("storage_insecure_testing_mode".into());
+ }
+ }
+
+ if matches!(
+ args.component,
+ Component::Firmware | Component::Bootloader | Component::Prodtest
+ ) {
+ if args.perf_overlay {
+ features.push("ui_performance_overlay".into());
+ }
+
+ if args.debug_link.unwrap_or(!pyopt) {
+ features.push("debuglink".into());
+ features.push("ui_debug".into());
+ }
+
+ if args.disable_animation {
+ features.push("disable_animation".into());
+ }
+ }
+
+ if matches!(args.component, Component::Kernel) {
+ if args.debug_link.unwrap_or(!pyopt) {
+ features.push("debuglink".into());
+ }
+ }
+
+ if args.component == Component::Firmware && (args.frozen || !args.emulator) {
+ features.push("frozen".into());
+ }
+
+ // Board and model-intrinsic features from TOML config
+ let model_config = config::ModelConfig::load(args.model.model_id())?;
+ let board_id = if args.emulator {
+ model_config
+ .emulator_board
+ .as_deref()
+ .ok_or_else(|| anyhow!("Model {} has no emulator board", args.model.model_id()))?
+ .to_string()
+ } else {
+ args.board
+ .clone()
+ .unwrap_or_else(|| model_config.default_board.clone())
+ };
+ let board_features = config::resolve_board_features(
+ args.model.model_id(),
+ &model_config,
+ &board_id,
+ args.component,
+ )?;
+ let mut board_feat = board_features.features;
+ if args.disable_optiga {
+ board_feat.retain(|f| f != "optiga");
+ }
+ if args.disable_tropic.unwrap_or(args.emulator) {
+ board_feat.retain(|f| f != "tropic");
+ }
+ features.extend(board_feat);
+
+ let target_triple = if args.emulator {
+ None
+ } else {
+ Some(model_config.target_triple()?)
+ };
+
+ Ok(ResolvedBuild {
+ features,
+ target_triple,
+ })
+}
+
+/// Configures a cargo command with the appropriate arguments and features.
+pub fn configure_cargo(args: &BuildArgs, cmd: &mut process::Command) -> Result<()> {
+ let resolved = resolve_features(args)?;
+ let mut rebuild_std = false;
+
+ cmd.args(["--package", args.component.package_name(args.emulator)]);
+ cmd.args(["--features", &resolved.features.join(",")]);
+ cmd.args(["--profile", args.profile_name()]);
+
+ if args.profile_name() == "release" {
+ // Required by panic-immediate-abort in the release profile
+ rebuild_std = true;
+ }
+
+ if let Some(triple) = resolved.target_triple {
+ cmd.args(["--target", triple]);
+ }
+
+ if args.emit_memory_analysis {
+ // See https://nnethercote.github.io/perf-book/type-sizes.html#measuring-type-sizes for more details
+ // Also adds an ELF section with Rust functions' stack sizes. See:
+ // - https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/emit-stack-sizes.html
+ // - https://blog.japaric.io/stack-analysis/
+ // - https://github.com/japaric/stack-sizes/
+ //
+ // Use --config instead of RUSTFLAGS env so that rustflags in .cargo/config.toml are
+ // not overridden (RUSTFLAGS env has higher precedence and replaces them entirely).
+ cmd.args([
+ "--config",
+ "build.rustflags=[\"-Zprint-type-sizes\", \"-Zemit-stack-sizes\"]",
+ ]);
+ }
+
+ if args.emulator && args.asan {
+ // -Zsanitizer=address is a rustc flag passed via RUSTFLAGS.
+ //
+ // Without an explicit --target, cargo compiles proc-macros and the firmware in the
+ // same pass and RUSTFLAGS leaks into proc-macro crates, causing "can't find crate"
+ // errors. Passing --target explicitly (even the same triple as the host) makes cargo
+ // separate the host (proc-macros / build scripts) and target (firmware) compilation
+ // units, so RUSTFLAGS only reaches the firmware crates.
+ cmd.args(["--target", &helpers::host_triple()?]);
+ cmd.args([
+ "--config",
+ "build.rustflags=[\"-Zsanitizer=address\", \"-Clink-arg=-lgcc_s\"]",
+ ]);
+
+ // Rebuild standard library to be compiled with sanitizer instrumentation
+ rebuild_std = true;
+ }
+
+ if args.timings {
+ cmd.arg("--timings");
+ }
+
+ if args.verbose {
+ cmd.arg("--verbose");
+ }
+
+ if rebuild_std {
+ cmd.arg("-Zbuild-std=core");
+ }
+
+ Ok(())
+}
diff --git a/core/embed/xtask/src/lib.rs b/core/embed/xtask/src/lib.rs
index 6de442ca..2f5abfec 100644
--- a/core/embed/xtask/src/lib.rs
+++ b/core/embed/xtask/src/lib.rs
@@ -3,6 +3,7 @@ pub mod artifacts;
pub mod cargo;
pub mod combine;
pub mod config;
+pub mod feature_resolver;
pub mod flash;
pub mod helpers;
pub mod memusage;
diff --git a/core/embed/xtask/src/postbuild.rs b/core/embed/xtask/src/postbuild.rs
index c302d656..040a4bea 100644
--- a/core/embed/xtask/src/postbuild.rs
+++ b/core/embed/xtask/src/postbuild.rs
@@ -6,90 +6,99 @@ use std::{
};
use crate::{
- args::{Component, Model},
+ args::Component,
+ config::{ModelConfig, TargetProfile},
helpers,
+ model::Model,
};
/// Extracts appropriate sections from the ELF file and creates a raw unsigned binary.
+/// Section lists are read from the component's `target.toml`; model-specific split
+/// behaviour is controlled by `model_config`.
pub fn elf_to_bin(
source: &Path,
component: Component,
- model: Model,
+ model_config: &ModelConfig,
use_dev_keys: bool,
) -> Result<PathBuf> {
- match component {
- Component::Boardloader => objcopy(
- source,
- [
- ".vector_table",
- ".text",
- ".data",
- ".rodata",
- ".capabilities",
- ],
- ),
-
- Component::Bootloader | Component::BootloaderCi => {
- objcopy(source, [".header", ".flash", ".data"])
- }
-
- Component::Secmon => objcopy(
- source,
- [".secmon_header", ".flash", ".data", ".gnu.sgstubs"],
- ),
-
- Component::Kernel => objcopy(source, [".flash", ".data"]),
+ let target_profile = TargetProfile::load(component)?;
+ match component {
Component::Firmware => {
- if matches!(model, Model::T2T1 | Model::T2B1 | Model::D001) {
- // On STM32F427 models, the firmware is not contiguous in flash.
- // It is split into two parts, with the storage area in between.
- // We therefore extract the two parts separately and concatenate them.
+ if model_config.is_stm32f4() {
+ // STM32F4 firmware flash is non-contiguous — two banks separated
+ // by the storage area must be extracted and concatenated.
+ // Part1 uses the same elf_sections as the flat (non-split) path.
+ let pad_to = target_profile
+ .split_pad_to
+ .as_deref()
+ .ok_or_else(|| anyhow::anyhow!("firmware target.toml missing split_pad_to"))?;
+ let part2_sections =
+ target_profile
+ .split_part2_sections
+ .as_ref()
+ .ok_or_else(|| {
+ anyhow::anyhow!("firmware target.toml missing split_part2_sections")
+ })?;
let part1 = objcopy_ex(
source,
"part1",
- [".vendorheader", ".header", ".flash", ".data"],
- ["--pad-to", "0x08100000"],
+ &target_profile.elf_sections,
+ ["--pad-to", pad_to],
)?;
- let part2 = objcopy_ex(source, "part2", [".flash2"], [] as [&str; 0])?;
+ let part2 = objcopy_ex(source, "part2", part2_sections, [] as [&str; 0])?;
concat_files(part1.with_extension("ubin"), [part1, part2])
} else {
- objcopy(source, [".vendorheader", ".header", ".flash", ".data"])
+ objcopy(source, &target_profile.elf_sections)
}
}
Component::Prodtest => {
- if matches!(model, Model::T3W1 | Model::D002) {
- let body_bin = objcopy_ex(
- source,
- "body.bin",
- [".secmon_header", ".flash", ".data"],
- [] as [&str; 0],
- )?;
-
- sign_binary(&body_bin, Component::Prodtest, model, use_dev_keys)?;
-
- let header_bin = objcopy_ex(
- source,
- "header.bin",
- [".vendorheader", ".header"],
- [] as [&str; 0],
- )?;
-
+ if model_config.secmon {
+ // On secmon models prodtest is a secmon-signed body with a plain
+ // vendor header prepended. The body is signed before concatenation.
+ let body_sections =
+ target_profile
+ .secmon_body_sections
+ .as_ref()
+ .ok_or_else(|| {
+ anyhow::anyhow!("prodtest target.toml missing secmon_body_sections")
+ })?;
+ let header_sections =
+ target_profile
+ .secmon_header_sections
+ .as_ref()
+ .ok_or_else(|| {
+ anyhow::anyhow!("prodtest target.toml missing secmon_header_sections")
+ })?;
+ let body_bin = objcopy_ex(source, "body.bin", body_sections, [] as [&str; 0])?;
+ sign_binary(&body_bin, component, model_config, use_dev_keys)?;
+ let header_bin =
+ objcopy_ex(source, "header.bin", header_sections, [] as [&str; 0])?;
concat_files(source.with_extension("bin"), [header_bin, body_bin])
} else {
- objcopy(source, [".vendorheader", ".header", ".flash", ".data"])
+ objcopy(source, &target_profile.elf_sections)
}
}
+
+ _ => objcopy(source, &target_profile.elf_sections),
}
}
pub fn sign_binary(
binary: &Path,
- target: Component,
- model: Model,
+ component: Component,
+ model_config: &ModelConfig,
use_dev_keys: bool,
) -> Result<()> {
+ let header_tool = match component {
+ Component::Bootloader | Component::BootloaderCi => model_config
+ .bootloader_header_tool
+ .as_deref()
+ .unwrap_or("headertool"),
+ _ => "headertool",
+ };
+
println!(
"xtask: Signing binary `{}`",
binary
@@ -98,8 +107,6 @@ pub fn sign_binary(
.to_string_lossy()
);
- let header_tool = header_tool_name(target, model);
-
let mut cmd = process::Command::new(header_tool);
// Rehash the header with the correct signature and keys
@@ -121,16 +128,6 @@ pub fn sign_binary(
Ok(())
}
-fn header_tool_name(target: Component, model: Model) -> &'static str {
- match (target, model) {
- (Component::Bootloader, Model::T3W1)
- | (Component::BootloaderCi, Model::T3W1)
- | (Component::Bootloader, Model::D002)
- | (Component::BootloaderCi, Model::D002) => "headertool_pq",
- _ => "headertool",
- }
-}
-
/// Extracts specified sections from an ELF file into a raw binary using objcopy.
/// The output file is created in the same directory as the input with the same name but .bin extension.
fn objcopy<S, I>(input: &Path, sections: I) -> Result<PathBuf>
@@ -142,9 +139,7 @@ where
}
/// A more flexible version of objcopy that allows specifying extra arguments
-/// and output extension. Used for the special case of the firmware on
-/// STM32F427 models, where we need to extract two separate parts of
-/// the ELF and concatenate them.
+/// and a custom output extension.
fn objcopy_ex<S1, S2, I1, I2>(
input: &Path,
output_extension: &str,
@@ -308,27 +303,10 @@ pub fn publish_artifact(
#[cfg(test)]
mod tests {
- use super::{header_tool_name, merge_compile_commands};
- use crate::args::{Component, Model};
+ use super::merge_compile_commands;
use serde_json::Value;
use std::fs;
- #[test]
- fn picks_pq_header_tool_only_for_supported_targets() {
- assert_eq!(
- header_tool_name(Component::Bootloader, Model::T3W1),
- "headertool_pq"
- );
- assert_eq!(
- header_tool_name(Component::BootloaderCi, Model::D002),
- "headertool_pq"
- );
- assert_eq!(
- header_tool_name(Component::Firmware, Model::T3W1),
- "headertool"
- );
- }
-
#[test]
fn merge_compile_commands_prefers_first_input_for_duplicates() {
let dir = tempfile::tempdir().unwrap();
Why this scored 11/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.