feat(core/sys): expose syslog as a Rust module
What changed, and why it matters
This commit is a routine internal code reorganization. It moves the syslog (system logging) functionality from one Rust module into a new shared 'sys' crate and updates the callers to use the new location. There is no user-facing change, no bug fix, and no security-related behavior change visible in the diff.
No security action required; review as normal refactoring if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change refactors syslog bindings: deletes core/embed/rust/src/trezorhal/syslog.rs, creates core/embed/sys/src/syslog.rs with a slightly safer wrapper (SyslogChunkWriter, Result return types, log closure), and updates micropython/logging.rs and util/logger.rs to import from sys::syslog. It also adds bindgen-generated FFI bindings in core/embed/sys/src/ffi.rs and wires the build script to expose syslog_start_record, syslog_write_chunk, log_source_t, and log_level_t. The functional behavior of logging remains the same; the patch is purely architectural.
Changed components
core/embed/rust/src/micropython/logging.rscore/embed/rust/src/util/logger.rscore/embed/sys/src/syslog.rscore/embed/sys/src/ffi.rscore/embed/sys/dbg/build.rsInspect captured patch +152 / −71
diff --git a/core/embed/Cargo.lock b/core/embed/Cargo.lock
index bf899f8c..873d2b42 100644
--- a/core/embed/Cargo.lock
+++ b/core/embed/Cargo.lock
@@ -893,10 +893,13 @@ dependencies = [
name = "sys"
version = "0.0.0"
dependencies = [
+ "bindgen",
"color-eyre",
"crypto",
+ "cty",
"models",
"rtl",
+ "ufmt",
"xbuild",
]
@@ -1018,6 +1021,7 @@ dependencies = [
"serde_json",
"spin",
"static-alloc",
+ "sys",
"trezor-thp",
"trezor-tjpgdec",
"ufmt",
diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml
index fddfb353..4c0ce910 100644
--- a/core/embed/rust/Cargo.toml
+++ b/core/embed/rust/Cargo.toml
@@ -61,7 +61,7 @@ telemetry = []
n4w1 = []
translations = ["crypto"]
secmon_layout = []
-dbg_console = []
+dbg_console = ["dep:sys"]
app_loading = []
thp = ["crypto/thp", "dep:trezor-thp", "dep:zeroize"]
test = [
@@ -120,6 +120,7 @@ without-alloc.workspace = true
crypto = { workspace = true, optional = true }
io = { workspace = true, optional = true }
+sys = { workspace = true, optional = true }
trezor-thp = { workspace = true, optional = true }
upymod = { workspace = true, optional = true }
zeroize = { workspace = true, optional = true }
diff --git a/core/embed/rust/src/micropython/logging.rs b/core/embed/rust/src/micropython/logging.rs
index aaff28a7..f5ac8090 100644
--- a/core/embed/rust/src/micropython/logging.rs
+++ b/core/embed/rust/src/micropython/logging.rs
@@ -1,3 +1,5 @@
+use sys::syslog::{log, LogLevel};
+
use crate::error::Error;
use crate::micropython::buffer::StrBuffer;
use crate::micropython::map::Map;
@@ -5,7 +7,6 @@ use crate::micropython::module::Module;
use crate::micropython::obj::Obj;
use crate::micropython::qstr::Qstr;
use crate::micropython::util;
-use crate::trezorhal::syslog::{syslog_start_record, syslog_write_chunk, LogLevel};
use crate::util::logger::init_rust_logging;
fn _log(level: LogLevel, args: &[Obj], kwargs: &Map) -> Result<Obj, Error> {
@@ -15,18 +16,22 @@ fn _log(level: LogLevel, args: &[Obj], kwargs: &Map) -> Result<Obj, Error> {
let module_name = StrBuffer::try_from(*module)?;
- if syslog_start_record(module_name.as_ref(), level) {
+ log(module_name.as_ref(), level, |writer| -> Result<(), Error> {
if let Ok(iface_obj) = kwargs.get(Qstr::MP_QSTR_iface) {
if iface_obj != Obj::const_none() {
let iface_type = iface_obj.type_().ok_or(Error::TypeError)?;
let iface_prefix = uformat!(len: 128, "\x1b[93m[{}]\x1b[0m ", iface_type.name());
- syslog_write_chunk(iface_prefix.as_ref(), false);
+ let Ok(_) = writer.write(iface_prefix.as_ref()) else {
+ // write failed -- return early, do not translate to error
+ return Ok(());
+ };
}
}
let msg: StrBuffer = util::modulo_format(*fmt, fmt_args)?.try_into()?;
- syslog_write_chunk(msg.as_ref(), true);
- }
+ let _ = writer.write(msg.as_ref()); // write failed -- doesn't matter at the end
+ Ok(())
+ })?;
Ok(Obj::const_none())
}
diff --git a/core/embed/rust/src/trezorhal/mod.rs b/core/embed/rust/src/trezorhal/mod.rs
index 73929dfe..b3dc96ae 100644
--- a/core/embed/rust/src/trezorhal/mod.rs
+++ b/core/embed/rust/src/trezorhal/mod.rs
@@ -54,6 +54,3 @@ pub mod irq;
#[cfg(feature = "nrf")]
pub mod nrf;
-
-#[cfg(feature = "dbg_console")]
-pub mod syslog;
diff --git a/core/embed/rust/src/trezorhal/syslog.rs b/core/embed/rust/src/trezorhal/syslog.rs
deleted file mode 100644
index 2b71a6d8..00000000
--- a/core/embed/rust/src/trezorhal/syslog.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-use super::ffi;
-
-#[derive(PartialEq, Debug, Eq, FromPrimitive, Clone, Copy)]
-pub enum LogLevel {
- Debug = ffi::log_level_t_LOG_LEVEL_DBG as _,
- Info = ffi::log_level_t_LOG_LEVEL_INF as _,
- Warn = ffi::log_level_t_LOG_LEVEL_WARN as _,
- Error = ffi::log_level_t_LOG_LEVEL_ERR as _,
-}
-
-impl ffi::log_source_t {
- fn new(module: &str) -> Self {
- Self {
- name: module.as_ptr() as *const cty::c_char,
- name_len: module.len(),
- }
- }
-}
-
-pub fn syslog_start_record(module: &str, level: LogLevel) -> bool {
- let syslog_info = ffi::log_source_t::new(module);
- unsafe {
- ffi::syslog_start_record(
- &syslog_info as *const ffi::log_source_t,
- level as ffi::log_level_t,
- )
- }
-}
-
-pub fn syslog_write_chunk(text: &str, end_record: bool) -> isize {
- unsafe { ffi::syslog_write_chunk(text.as_ptr() as *const cty::c_char, text.len(), end_record) }
-}
diff --git a/core/embed/rust/src/util/logger.rs b/core/embed/rust/src/util/logger.rs
index a3221062..4bb44317 100644
--- a/core/embed/rust/src/util/logger.rs
+++ b/core/embed/rust/src/util/logger.rs
@@ -1,29 +1,22 @@
//! Connects the `log::error!`, `log::warn!`, ... macros from the `log` crate to
//! our C logging backend.
-use core::fmt::Write;
+use core::fmt::Write as _;
use core::sync::atomic::{AtomicBool, Ordering};
-use heapless::Vec;
use log::{set_logger, set_max_level, Level, LevelFilter, Log, Metadata, Record};
-
-use crate::trezorhal::syslog::{syslog_start_record, syslog_write_chunk, LogLevel};
-
-#[cfg(test)]
-const MAX_MESSAGE_LEN: usize = 512;
-#[cfg(not(test))]
-const MAX_MESSAGE_LEN: usize = 128;
+use sys::syslog;
static INITIALIZED: AtomicBool = AtomicBool::new(false);
struct SysLogger;
-fn sys_level(level: Level) -> LogLevel {
+fn sys_level(level: Level) -> syslog::LogLevel {
match level {
- Level::Error => LogLevel::Error,
- Level::Warn => LogLevel::Warn,
- Level::Info => LogLevel::Info,
- Level::Debug | Level::Trace => LogLevel::Debug,
+ Level::Error => syslog::LogLevel::Error,
+ Level::Warn => syslog::LogLevel::Warn,
+ Level::Info => syslog::LogLevel::Info,
+ Level::Debug | Level::Trace => syslog::LogLevel::Debug,
}
}
@@ -40,22 +33,10 @@ impl Log for SysLogger {
return;
}
- let should_log = syslog_start_record(record.target(), sys_level(record.level()));
- if !should_log {
- return;
- }
-
- let mut msg = Vec::<u8, MAX_MESSAGE_LEN>::new();
- // Might still get partial message on error.
- let res = msg.write_fmt(*record.args());
-
- // SAFETY: passed to C which doesn't care about UTF-8
- let text = unsafe { str::from_utf8_unchecked(&msg) };
- syslog_write_chunk(text, res.is_ok());
-
- if res.is_err() {
- syslog_write_chunk("(message truncated)", true);
- }
+ syslog::log(record.target(), sys_level(record.level()), |write| {
+ write.write_fmt(*record.args())
+ })
+ .ok();
}
fn flush(&self) {}
diff --git a/core/embed/sys/Cargo.toml b/core/embed/sys/Cargo.toml
index 5d11a97d..38dc52ff 100644
--- a/core/embed/sys/Cargo.toml
+++ b/core/embed/sys/Cargo.toml
@@ -5,13 +5,16 @@ edition = "2024"
links = "sys"
[build-dependencies]
+bindgen.workspace = true
color-eyre.workspace = true
xbuild.workspace = true
[dependencies]
crypto.workspace = true
+cty.workspace = true
rtl.workspace = true
models.workspace = true
+ufmt.workspace = true
[features]
diff --git a/core/embed/sys/dbg/build.rs b/core/embed/sys/dbg/build.rs
index 10f1fa07..e7658fb3 100644
--- a/core/embed/sys/dbg/build.rs
+++ b/core/embed/sys/dbg/build.rs
@@ -7,6 +7,8 @@ pub fn def_module(lib: &mut CLibrary) -> Result<()> {
lib.add_sources(["dbg/dbg_console.c", "dbg/syslog.c"]);
+ lib.add_rust_bindings(add_rust_bindings)?;
+
if cfg!(feature = "emulator") {
lib.add_source("dbg/unix/dbg_console_backend.c");
} else if cfg!(feature = "mcu_stm32") {
@@ -53,3 +55,14 @@ fn def_system_view(lib: &mut CLibrary) {
],
);
}
+
+fn add_rust_bindings(builder: bindgen::Builder) -> Result<bindgen::Builder> {
+ let builder = builder
+ .header("inc/sys/logging.h")
+ .header("dbg/inc/sys/syslog.h")
+ .allowlist_function("syslog_start_record")
+ .allowlist_function("syslog_write_chunk")
+ .allowlist_type("log_source_t")
+ .allowlist_type("log_level_t");
+ Ok(builder)
+}
diff --git a/core/embed/sys/src/ffi.rs b/core/embed/sys/src/ffi.rs
new file mode 100644
index 00000000..58380be3
--- /dev/null
+++ b/core/embed/sys/src/ffi.rs
@@ -0,0 +1,5 @@
+#![allow(non_camel_case_types)]
+#![allow(non_upper_case_globals)]
+#![allow(dead_code)]
+
+include!(concat!(env!("OUT_DIR"), "/sys.rs"));
diff --git a/core/embed/sys/src/lib.rs b/core/embed/sys/src/lib.rs
index 0c9ac1ac..bcfe9b21 100644
--- a/core/embed/sys/src/lib.rs
+++ b/core/embed/sys/src/lib.rs
@@ -1 +1,6 @@
#![no_std]
+
+mod ffi;
+
+#[cfg(feature = "dbg_console")]
+pub mod syslog;
diff --git a/core/embed/sys/src/syslog.rs b/core/embed/sys/src/syslog.rs
new file mode 100644
index 00000000..1ca5908b
--- /dev/null
+++ b/core/embed/sys/src/syslog.rs
@@ -0,0 +1,99 @@
+use super::ffi;
+
+#[derive(PartialEq, Debug, Eq, Clone, Copy)]
+pub enum LogLevel {
+ Debug = ffi::log_level_t_LOG_LEVEL_DBG as _,
+ Info = ffi::log_level_t_LOG_LEVEL_INF as _,
+ Warn = ffi::log_level_t_LOG_LEVEL_WARN as _,
+ Error = ffi::log_level_t_LOG_LEVEL_ERR as _,
+}
+
+impl ffi::log_source_t {
+ fn new(module: &str) -> Self {
+ Self {
+ name: module.as_ptr() as *const cty::c_char,
+ name_len: module.len(),
+ }
+ }
+}
+
+fn syslog_start_record(module: &str, level: LogLevel) -> bool {
+ let syslog_info = ffi::log_source_t::new(module);
+ unsafe {
+ ffi::syslog_start_record(
+ &syslog_info as *const ffi::log_source_t,
+ level as ffi::log_level_t,
+ )
+ }
+}
+
+fn syslog_write_chunk(text: &str, end_record: bool) -> Result<usize, ()> {
+ let bytes_written = unsafe {
+ ffi::syslog_write_chunk(text.as_ptr() as *const cty::c_char, text.len(), end_record)
+ };
+ if bytes_written < 0 {
+ Err(())
+ } else {
+ Ok(bytes_written as usize)
+ }
+}
+
+/// Object for writing to the syslog chunk.
+///
+/// Has a private field to prevent construction outside of this module.
+/// Assumes that whoever owns the instance has properly started a new record
+/// via [`syslog_start_record`], and will properly end the record via
+/// [`syslog_write_chunk`] with `end_record` set to `true`.
+///
+/// Implements `core::fmt::Write` for callers that need it.
+pub struct SyslogChunkWriter(());
+
+impl SyslogChunkWriter {
+ pub fn write(&self, s: &str) -> Result<(), ()> {
+ syslog_write_chunk(s, false)?;
+ // TODO: check write length?
+ Ok(())
+ }
+}
+
+impl core::fmt::Write for SyslogChunkWriter {
+ fn write_str(&mut self, s: &str) -> core::fmt::Result {
+ self.write(s).map_err(|_| core::fmt::Error)
+ }
+}
+
+impl ufmt::uWrite for SyslogChunkWriter {
+ type Error = ();
+
+ fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
+ self.write(s)
+ }
+}
+
+pub fn log<T, E, F>(module: &str, level: LogLevel, log_fn: F) -> Result<Option<T>, E>
+where
+ F: FnOnce(&mut SyslogChunkWriter) -> Result<T, E>,
+{
+ if syslog_start_record(module, level) {
+ let mut writer = SyslogChunkWriter(());
+
+ match log_fn(&mut writer) {
+ Ok(result) => {
+ syslog_write_chunk("", true).ok();
+ Ok(Some(result))
+ }
+ Err(e) => {
+ syslog_write_chunk("#INTERRUPTED", true).ok();
+ Err(e)
+ }
+ }
+ } else {
+ Ok(None)
+ }
+}
+
+pub fn log_simple(module: &str, level: LogLevel, message: &str) {
+ if syslog_start_record(module, level) {
+ syslog_write_chunk(message, true).ok();
+ }
+}
Why this scored 15/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.