Skip to content

Commit a755924

Browse files
committed
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.
1 parent c15f2c6 commit a755924

File tree

5 files changed

+43
-6
lines changed

5 files changed

+43
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99

10+
- [#878]: Hex-encode defmt symbols to avoid compatibility issues.
1011
- [#874]: `defmt`: Fix doc test
1112
- [#872]: `defmt`: Add `expect!` as alias for `unwrap!` for discoverability
1213
- [#871]: Set MSRV to Rust 1.76

decoder/src/elf2table/symbol.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use std::borrow::Cow;
2+
3+
use anyhow::bail;
14
use serde::Deserialize;
25

36
use crate::Tag;
@@ -38,7 +41,11 @@ pub(super) enum SymbolTag {
3841

3942
impl Symbol {
4043
pub fn demangle(raw: &str) -> anyhow::Result<Self> {
41-
serde_json::from_str(raw)
44+
let mut raw = Cow::from(raw);
45+
if let Some(s) = raw.strip_prefix("__defmt_") {
46+
raw = Cow::from(hex_decode(s)?);
47+
}
48+
serde_json::from_str(&raw)
4249
.map_err(|j| anyhow::anyhow!("failed to demangle defmt symbol `{}`: {}", raw, j))
4350
}
4451

@@ -77,3 +84,23 @@ impl Symbol {
7784
self.crate_name.as_deref()
7885
}
7986
}
87+
88+
fn hex_decode_digit(c: u8) -> anyhow::Result<u8> {
89+
match c {
90+
b'0'..=b'9' => Ok(c - b'0'),
91+
b'a'..=b'f' => Ok(c - b'a' + 0xa),
92+
_ => bail!("invalid hex char '{c}'"),
93+
}
94+
}
95+
96+
fn hex_decode(s: &str) -> anyhow::Result<String> {
97+
let s = s.as_bytes();
98+
if s.len() % 2 != 0 {
99+
bail!("invalid hex: length must be even")
100+
}
101+
let mut res = vec![0u8; s.len() / 2];
102+
for i in 0..(s.len() / 2) {
103+
res[i] = (hex_decode_digit(s[i * 2])? << 4) | hex_decode_digit(s[i * 2 + 1])?;
104+
}
105+
Ok(String::from_utf8(res)?)
106+
}

decoder/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
#![cfg_attr(docsrs, doc(cfg(unstable)))]
88
#![doc(html_logo_url = "https://knurling.ferrous-systems.com/knurling_logo_light_text.svg")]
99

10-
pub const DEFMT_VERSIONS: &[&str] = &["3", "4"];
10+
pub const DEFMT_VERSIONS: &[&str] = &["3", "4", "5"];
1111
// To avoid a breaking change, still provide `DEFMT_VERSION`.
1212
#[deprecated = "Please use DEFMT_VERSIONS instead"]
13-
pub const DEFMT_VERSION: &str = DEFMT_VERSIONS[1];
13+
pub const DEFMT_VERSION: &str = DEFMT_VERSIONS[DEFMT_VERSIONS.len() - 1];
1414

1515
mod decoder;
1616
mod elf2table;

defmt/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extern crate alloc;
2929
#[used]
3030
#[cfg_attr(target_os = "macos", link_section = ".defmt,end.VERSION")]
3131
#[cfg_attr(not(target_os = "macos"), link_section = ".defmt.end")]
32-
#[export_name = "_defmt_version_ = 4"]
32+
#[export_name = "_defmt_version_ = 5"]
3333
static DEFMT_VERSION: u8 = 0;
3434

3535
#[used]

macros/src/construct/symbol.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,26 @@ impl<'a> Symbol<'a> {
5252
}
5353

5454
fn mangle(&self) -> String {
55-
format!(
55+
let json = format!(
5656
r#"{{"package":"{}","tag":"{}","data":"{}","disambiguator":"{}","crate_name":"{}"}}"#,
5757
json_escape(&self.package),
5858
json_escape(&self.tag),
5959
json_escape(self.data),
6060
self.disambiguator,
6161
json_escape(&self.crate_name),
62-
)
62+
);
63+
format!("__defmt_{}", hex_encode(&json))
6364
}
6465
}
6566

67+
fn hex_encode(string: &str) -> String {
68+
let mut res = String::new();
69+
for &b in string.as_bytes() {
70+
write!(&mut res, "{b:02x}").unwrap();
71+
}
72+
res
73+
}
74+
6675
fn json_escape(string: &str) -> String {
6776
let mut escaped = String::new();
6877
for c in string.chars() {

0 commit comments

Comments
 (0)