Skip to content

Commit

Permalink
Hex-encode defmt symbols to avoid compatibility issues.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Dirbaio committed Oct 28, 2024
1 parent c15f2c6 commit 55524c8
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 6 deletions.
29 changes: 28 additions & 1 deletion decoder/src/elf2table/symbol.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::borrow::Cow;

use anyhow::bail;
use serde::Deserialize;

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

impl Symbol {
pub fn demangle(raw: &str) -> anyhow::Result<Self> {
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))
}

Expand Down Expand Up @@ -77,3 +84,23 @@ impl Symbol {
self.crate_name.as_deref()
}
}

fn hex_decode_digit(c: u8) -> anyhow::Result<u8> {
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<String> {
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)?)
}
4 changes: 2 additions & 2 deletions decoder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion defmt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
13 changes: 11 additions & 2 deletions macros/src/construct/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 55524c8

Please sign in to comment.