Skip to content

Commit

Permalink
Add encoding to elf symbols, use it when decoding.
Browse files Browse the repository at this point in the history
  • Loading branch information
Dirbaio committed Jul 18, 2021
1 parent e5717d4 commit 0c8d76e
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 33 deletions.
70 changes: 52 additions & 18 deletions decoder/src/elf2table/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,37 @@ use std::{
path::{Path, PathBuf},
};

use crate::{BitflagsKey, StringEntry, Table, TableEntry, Tag, DEFMT_VERSION};
use crate::{BitflagsKey, Encoding, StringEntry, Table, TableEntry, Tag, DEFMT_VERSION};
use anyhow::{anyhow, bail, ensure};
use object::{Object, ObjectSection, ObjectSymbol};

pub fn parse_impl(elf: &[u8], check_version: bool) -> Result<Option<Table>, anyhow::Error> {
let elf = object::File::parse(elf)?;
// first pass to extract the `_defmt_version`
let mut version = None;
let is_defmt_version = |name: &str| {
name.starts_with("\"_defmt_version_ = ") || name.starts_with("_defmt_version_ = ")
let mut encoding = None;

// Note that we check for a quoted and unquoted version symbol, since LLD has a bug that
// makes it keep the quotes from the linker script.
let try_get_version = |name: &str| {
if name.starts_with("\"_defmt_version_ = ") || name.starts_with("_defmt_version_ = ") {
Some(
name.trim_start_matches("\"_defmt_version_ = ")
.trim_start_matches("_defmt_version_ = ")
.trim_end_matches('"')
.to_string(),
)
} else {
None
}
};

// No need to remove quotes for `_defmt_encoding_`, since it's
let try_get_encoding = |name: &str| {
name.strip_prefix("_defmt_encoding_ = ")
.map(ToString::to_string)
};

for entry in elf.symbols() {
let name = match entry.name() {
Ok(name) => name,
Expand All @@ -32,13 +52,7 @@ pub fn parse_impl(elf: &[u8], check_version: bool) -> Result<Option<Table>, anyh

// Not in the `.defmt` section because it's not tied to the address of any symbol
// in `.defmt`.
// Note that we check for a quoted and unquoted version symbol, since LLD has a bug that
// makes it keep the quotes from the linker script.
if is_defmt_version(name) {
let new_version = name
.trim_start_matches("\"_defmt_version_ = ")
.trim_start_matches("_defmt_version_ = ")
.trim_end_matches('"');
if let Some(new_version) = try_get_version(name) {
if let Some(version) = version {
return Err(anyhow!(
"multiple defmt versions in use: {} and {} (only one is supported)",
Expand All @@ -48,6 +62,17 @@ pub fn parse_impl(elf: &[u8], check_version: bool) -> Result<Option<Table>, anyh
}
version = Some(new_version);
}

if let Some(new_encoding) = try_get_encoding(name) {
if let Some(encoding) = encoding {
return Err(anyhow!(
"multiple defmt encodings in use: {} and {} (only one is supported)",
encoding,
new_encoding
));
}
encoding = Some(new_encoding);
}
}

// NOTE: We need to make sure to return `Ok(None)`, not `Err`, when defmt is not in use.
Expand All @@ -69,9 +94,18 @@ pub fn parse_impl(elf: &[u8], check_version: bool) -> Result<Option<Table>, anyh
};

if check_version {
self::check_version(version).map_err(anyhow::Error::msg)?;
self::check_version(&version).map_err(anyhow::Error::msg)?;
}

let encoding = match encoding {
Some(e) => match &e[..] {
"raw" => Encoding::Raw,
"rzcobs" => Encoding::Rzcobs,
_ => bail!("Unknown defmt encoding '{}' specified. This is a bug.", e),
},
None => bail!("No defmt encoding specified. This is a bug."),
};

// second pass to demangle symbols
let mut map = BTreeMap::new();
let mut bitflags_map = HashMap::new();
Expand All @@ -84,7 +118,7 @@ pub fn parse_impl(elf: &[u8], check_version: bool) -> Result<Option<Table>, anyh
_ => continue,
};

if is_defmt_version(name) || name.starts_with("__DEFMT_MARKER") {
if name.starts_with("_defmt") || name.starts_with("__DEFMT_MARKER") {
// `_defmt_version_` is not a JSON encoded `defmt` symbol / log-message; skip it
// LLD and GNU LD behave differently here. LLD doesn't include `_defmt_version_`
// (defined in a linker script) in the `.defmt` section but GNU LD does.
Expand Down Expand Up @@ -157,12 +191,12 @@ pub fn parse_impl(elf: &[u8], check_version: bool) -> Result<Option<Table>, anyh
}
}

let mut table = Table::new(map);
if let Some(ts) = timestamp {
table.set_timestamp_entry(ts);
}
table.bitflags = bitflags_map;
Ok(Some(table))
Ok(Some(Table {
entries: map,
timestamp,
bitflags: bitflags_map,
encoding,
}))
}

/// Checks if the version encoded in the symbol table is compatible with this version of the `decoder` crate
Expand Down
30 changes: 15 additions & 15 deletions decoder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,24 +116,22 @@ struct BitflagsKey {
disambig: String,
}

#[derive(Copy, Clone, Debug, PartialEq)]
enum Encoding {
Raw,
Rzcobs,
}

/// Internal table that holds log levels and maps format strings to indices
#[derive(Debug, PartialEq)]
pub struct Table {
timestamp: Option<TableEntry>,
entries: BTreeMap<usize, TableEntry>,
bitflags: HashMap<BitflagsKey, Vec<(String, u128)>>,
encoding: Encoding,
}

impl Table {
/// NOTE caller must verify that defmt symbols are compatible with this version of the `decoder` crate using the `check_version` function
pub fn new(entries: BTreeMap<usize, TableEntry>) -> Self {
Self {
entries,
timestamp: None,
bitflags: Default::default(),
}
}

/// Parses an ELF file and returns the decoded `defmt` table.
///
/// This function returns `None` if the ELF file contains no `.defmt` section.
Expand Down Expand Up @@ -236,12 +234,10 @@ impl Table {
}

pub fn new_stream_decoder(&self) -> Box<dyn StreamDecoder + '_> {
Box::new(stream::Rzcobs::new(self))
}

// temporary, will remove
pub fn new_stream_decoder_raw(&self) -> Box<dyn StreamDecoder + '_> {
Box::new(stream::Raw::new(self))
match self.encoding {
Encoding::Raw => Box::new(stream::Raw::new(self)),
Encoding::Rzcobs => Box::new(stream::Rzcobs::new(self)),
}
}
}

Expand Down Expand Up @@ -326,6 +322,7 @@ mod tests {
timestamp: None,
entries: entries.into_iter().enumerate().collect(),
bitflags: Default::default(),
encoding: Encoding::Raw,
}
}

Expand All @@ -340,6 +337,7 @@ mod tests {
)),
entries: entries.into_iter().enumerate().collect(),
bitflags: Default::default(),
encoding: Encoding::Raw,
}
}

Expand All @@ -362,6 +360,7 @@ mod tests {
"{=u8:us}".to_owned(),
)),
bitflags: Default::default(),
encoding: Encoding::Raw,
};

let frame = table.decode(&bytes).unwrap().0;
Expand Down Expand Up @@ -1021,6 +1020,7 @@ mod tests {
"{=u8:us}".to_owned(),
)),
bitflags: Default::default(),
encoding: Encoding::Raw,
};

let bytes = [
Expand Down
12 changes: 12 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@
#[cfg(feature = "alloc")]
extern crate alloc;

// This must be in the root lib.rs, otherwise it doesn't appear in the final binary.
#[used]
#[link_section = ".defmt.end"]
#[cfg_attr(feature = "encoding-raw", export_name = "_defmt_encoding_ = raw")]
#[cfg_attr(
not(feature = "encoding-raw"),
export_name = "_defmt_encoding_ = rzcobs"
)]
#[allow(missing_docs)]
#[doc(hidden)]
pub static DEFMT_ENCODING: u8 = 0;

mod encoding;
#[doc(hidden)]
pub mod export;
Expand Down

0 comments on commit 0c8d76e

Please sign in to comment.