From 55524c8d9115736658384a37388f208660c9cb68 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Oct 2024 19:16:00 +0100 Subject: [PATCH] Hex-encode defmt symbols to avoid compatibility issues. defmt currently puts json as-is in symbol names, which can contain special characters not normally found in symbol names like quotes `"`, braces `{}` and spaces ` `. This can cause compatibility issues with language features or tools that don't expect this. For example it breaks `sym` in `asm!`. This is a *breaking change* of the wire format, so this PR increases the format numbre. `defmt-decoder` is updated to be able to decode the new format, while keeping ability to decode older formats. --- decoder/src/elf2table/symbol.rs | 29 ++++++++++++++++++++++++++++- decoder/src/lib.rs | 4 ++-- defmt/src/lib.rs | 2 +- macros/src/construct/symbol.rs | 13 +++++++++++-- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/decoder/src/elf2table/symbol.rs b/decoder/src/elf2table/symbol.rs index ce35d4e1..eeb4a416 100644 --- a/decoder/src/elf2table/symbol.rs +++ b/decoder/src/elf2table/symbol.rs @@ -1,3 +1,6 @@ +use std::borrow::Cow; + +use anyhow::bail; use serde::Deserialize; use crate::Tag; @@ -38,7 +41,11 @@ pub(super) enum SymbolTag { impl Symbol { pub fn demangle(raw: &str) -> anyhow::Result { - serde_json::from_str(raw) + let mut raw = Cow::from(raw); + if let Some(s) = raw.strip_prefix("__defmt_") { + raw = Cow::from(hex_decode(s)?); + } + serde_json::from_str(&raw) .map_err(|j| anyhow::anyhow!("failed to demangle defmt symbol `{}`: {}", raw, j)) } @@ -77,3 +84,23 @@ impl Symbol { self.crate_name.as_deref() } } + +fn hex_decode_digit(c: u8) -> anyhow::Result { + match c { + b'0'..=b'9' => Ok(c - b'0'), + b'a'..=b'f' => Ok(c - b'a' + 0xa), + _ => bail!("invalid hex char '{c}'"), + } +} + +fn hex_decode(s: &str) -> anyhow::Result { + let s = s.as_bytes(); + if s.len() % 2 != 0 { + bail!("invalid hex: length must be even") + } + let mut res = vec![0u8; s.len() / 2]; + for i in 0..(s.len() / 2) { + res[i] = (hex_decode_digit(s[i * 2])? << 4) | hex_decode_digit(s[i * 2 + 1])?; + } + Ok(String::from_utf8(res)?) +} diff --git a/decoder/src/lib.rs b/decoder/src/lib.rs index affc01e7..9f893a20 100644 --- a/decoder/src/lib.rs +++ b/decoder/src/lib.rs @@ -7,10 +7,10 @@ #![cfg_attr(docsrs, doc(cfg(unstable)))] #![doc(html_logo_url = "https://knurling.ferrous-systems.com/knurling_logo_light_text.svg")] -pub const DEFMT_VERSIONS: &[&str] = &["3", "4"]; +pub const DEFMT_VERSIONS: &[&str] = &["3", "4", "5"]; // To avoid a breaking change, still provide `DEFMT_VERSION`. #[deprecated = "Please use DEFMT_VERSIONS instead"] -pub const DEFMT_VERSION: &str = DEFMT_VERSIONS[1]; +pub const DEFMT_VERSION: &str = DEFMT_VERSIONS[DEFMT_VERSIONS.len() - 1]; mod decoder; mod elf2table; diff --git a/defmt/src/lib.rs b/defmt/src/lib.rs index abffdf8f..ec1d793e 100644 --- a/defmt/src/lib.rs +++ b/defmt/src/lib.rs @@ -29,7 +29,7 @@ extern crate alloc; #[used] #[cfg_attr(target_os = "macos", link_section = ".defmt,end.VERSION")] #[cfg_attr(not(target_os = "macos"), link_section = ".defmt.end")] -#[export_name = "_defmt_version_ = 4"] +#[export_name = "_defmt_version_ = 5"] static DEFMT_VERSION: u8 = 0; #[used] diff --git a/macros/src/construct/symbol.rs b/macros/src/construct/symbol.rs index 77eae0b2..f668e26e 100644 --- a/macros/src/construct/symbol.rs +++ b/macros/src/construct/symbol.rs @@ -52,17 +52,26 @@ impl<'a> Symbol<'a> { } fn mangle(&self) -> String { - format!( + let json = format!( r#"{{"package":"{}","tag":"{}","data":"{}","disambiguator":"{}","crate_name":"{}"}}"#, json_escape(&self.package), json_escape(&self.tag), json_escape(self.data), self.disambiguator, json_escape(&self.crate_name), - ) + ); + format!("__defmt_{}", hex_encode(&json)) } } +fn hex_encode(string: &str) -> String { + let mut res = String::new(); + for &b in string.as_bytes() { + write!(&mut res, "{b:02x}").unwrap(); + } + res +} + fn json_escape(string: &str) -> String { let mut escaped = String::new(); for c in string.chars() {