Skip to content

Commit

Permalink
Merge pull request #148 from knurling-rs/locs
Browse files Browse the repository at this point in the history
extract location info (file+line number) from DWARF
  • Loading branch information
japaric authored Aug 28, 2020
2 parents 34abded + 43c0876 commit a62fe2f
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 14 deletions.
16 changes: 16 additions & 0 deletions decoder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,17 @@ impl Table {
Err(())
}
}

pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}

/// A log frame
#[derive(Debug, PartialEq)]
pub struct Frame<'t> {
level: Level,
index: u64,
// Format string
format: &'t str,
timestamp: u64,
Expand All @@ -122,6 +127,10 @@ impl<'t> Frame<'t> {
colored,
}
}

pub fn index(&self) -> u64 {
self.index
}
}

pub struct DisplayFrame<'t> {
Expand Down Expand Up @@ -244,6 +253,7 @@ pub fn decode<'t>(

let frame = Frame {
level,
index,
format,
timestamp,
args,
Expand Down Expand Up @@ -720,6 +730,7 @@ mod tests {
super::decode(&bytes, &table),
Ok((
Frame {
index: 0,
level: Level::Info,
format: "Hello, world!",
timestamp: 1,
Expand All @@ -739,6 +750,7 @@ mod tests {
super::decode(&bytes, &table),
Ok((
Frame {
index: 1,
level: Level::Debug,
format: "The answer is {:u8}!",
timestamp: 2,
Expand Down Expand Up @@ -782,6 +794,7 @@ mod tests {
super::decode(&bytes, &table),
Ok((
Frame {
index: 0,
level: Level::Info,
format: FMT,
timestamp: 2,
Expand Down Expand Up @@ -824,6 +837,7 @@ mod tests {
super::decode(&bytes, &table),
Ok((
Frame {
index: 0,
level: Level::Info,
format: "The answer is {0:u8} {0:u8}!",
timestamp: 2,
Expand All @@ -844,6 +858,7 @@ mod tests {
super::decode(&bytes, &table),
Ok((
Frame {
index: 1,
level: Level::Info,
format: "The answer is {1:u16} {0:u8} {1:u16}!",
timestamp: 2,
Expand Down Expand Up @@ -880,6 +895,7 @@ mod tests {
super::decode(&bytes, &table),
Ok((
Frame {
index: 0,
level: Level::Info,
format: "x={:?}",
timestamp: 2,
Expand Down
9 changes: 9 additions & 0 deletions defmt.x.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ SECTIONS
{
.defmt (INFO) :
{
/* this section starts at address 1 */
/* we use this fact when extract file locations from the DWARF information.
symbols that appear in source code but won't make it to the final program
(e.g. `if false { error!("foo")}`) will be given an address of 0 in DWARF
to differentiate those "ghost" symbols from symbols that will be used, we
have the latter start at address 1
*/
. = 1;

/* Format implementations for primitives like u8 */
*(.defmt.prim.*);

Expand Down
3 changes: 2 additions & 1 deletion elf2table/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ name = "elf2table"
version = "0.1.0"

[dependencies]
decoder = { path = "../decoder" }
anyhow = "1.0.32"
decoder = { path = "../decoder" }
gimli = "0.22.0"

[dependencies.object]
version = "0.21.0"
Expand Down
195 changes: 190 additions & 5 deletions elf2table/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
//! Reads ELF metadata and builds an interner table
use std::collections::BTreeMap;
use std::{
borrow::Cow,
collections::BTreeMap,
path::{Path, PathBuf},
};

use anyhow::anyhow;
use anyhow::{anyhow, bail, ensure};
pub use decoder::Table;
use object::{File, Object, ObjectSection};
use object::{Object, ObjectSection};

/// Parses an ELF file and returns the decoded `defmt` table
///
/// This function returns `None` if the ELF file contains no `.defmt` section
pub fn parse(elf: &[u8]) -> Result<Option<Table>, anyhow::Error> {
let elf = File::parse(elf)?;

let elf = object::File::parse(elf)?;
// find the index of the `.defmt` section
let defmt_shndx = if let Some(section) = elf.section_by_name(".defmt") {
section.index()
Expand Down Expand Up @@ -92,3 +95,185 @@ pub fn parse(elf: &[u8]) -> Result<Option<Table>, anyhow::Error> {
.map_err(anyhow::Error::msg)
.map(Some)
}

#[derive(Debug)]
pub struct Location {
pub file: PathBuf,
pub line: u64,
}

pub type Locations = BTreeMap<u64, Location>;

pub fn get_locations(elf: &[u8]) -> Result<Locations, anyhow::Error> {
let object = object::File::parse(elf)?;
let endian = if object.is_little_endian() {
gimli::RunTimeEndian::Little
} else {
gimli::RunTimeEndian::Big
};

let load_section = |id: gimli::SectionId| {
Ok(if let Some(s) = object.section_by_name(id.name()) {
s.uncompressed_data().unwrap_or(Cow::Borrowed(&[][..]))
} else {
Cow::Borrowed(&[][..])
})
};
let load_section_sup = |_| Ok(Cow::Borrowed(&[][..]));

let dwarf_cow =
gimli::Dwarf::<Cow<[u8]>>::load::<_, _, anyhow::Error>(&load_section, &load_section_sup)?;

let borrow_section: &dyn for<'a> Fn(
&'a Cow<[u8]>,
) -> gimli::EndianSlice<'a, gimli::RunTimeEndian> =
&|section| gimli::EndianSlice::new(&*section, endian);

let dwarf = dwarf_cow.borrow(&borrow_section);

let mut units = dwarf.debug_info.units();

let mut map = BTreeMap::new();
while let Some(header) = units.next()? {
let unit = dwarf.unit(header)?;
let abbrev = header.abbreviations(&dwarf.debug_abbrev)?;

let mut cursor = header.entries(&abbrev);

ensure!(cursor.next_dfs()?.is_some(), "empty DWARF?");

while let Some((_, entry)) = cursor.next_dfs()? {
// NOTE .. here start the custom logic
if entry.tag() == gimli::constants::DW_TAG_variable {
// Iterate over the attributes in the DIE.
let mut attrs = entry.attrs();

// what we are after
let mut decl_file = None;
let mut decl_line = None; // line number
let mut name = None;
let mut location = None;

while let Some(attr) = attrs.next()? {
match attr.name() {
gimli::constants::DW_AT_name => {
if let gimli::AttributeValue::DebugStrRef(off) = attr.value() {
name = Some(off);
}
}

gimli::constants::DW_AT_decl_file => {
if let gimli::AttributeValue::FileIndex(idx) = attr.value() {
decl_file = Some(idx);
}
}

gimli::constants::DW_AT_decl_line => {
if let gimli::AttributeValue::Udata(line) = attr.value() {
decl_line = Some(line);
}
}

gimli::constants::DW_AT_location => {
if let gimli::AttributeValue::Exprloc(loc) = attr.value() {
location = Some(loc);
}
}

_ => {}
}
}

if name.is_some()
&& decl_file.is_some()
&& decl_line.is_some()
&& location.is_some()
{
if let (Some(name_index), Some(file_index), Some(line), Some(loc)) =
(name, decl_file, decl_line, location)
{
let endian_slice = dwarf.string(name_index)?;
let name = core::str::from_utf8(&endian_slice)?;

if name == "DEFMT_LOG_STATEMENT" {
let addr = exprloc2address(unit.encoding(), &loc)?;
let file = file_index_to_path(file_index, &unit, &dwarf)?;

let loc = Location { file, line };

if addr != 0 {
ensure!(
map.insert(addr, loc).is_none(),
"BUG in DWARF variable filter: index collision"
);
}
}
}
}
}
}
}

Ok(map)
}

fn file_index_to_path<R>(
index: u64,
unit: &gimli::Unit<R>,
dwarf: &gimli::Dwarf<R>,
) -> Result<PathBuf, anyhow::Error>
where
R: gimli::read::Reader,
{
ensure!(index != 0, "`FileIndex` was zero");

let header = if let Some(program) = &unit.line_program {
program.header()
} else {
bail!("no `LineProgram`");
};

let file = if let Some(file) = header.file(index) {
file
} else {
bail!("no `FileEntry` for index {}", index)
};

let mut p = PathBuf::new();
if let Some(dir) = file.directory(header) {
let dir = dwarf.attr_string(unit, dir)?;
let dir_s = dir.to_string_lossy()?;
let dir = Path::new(&dir_s[..]);

if !dir.is_absolute() {
if let Some(ref comp_dir) = unit.comp_dir {
p.push(&comp_dir.to_string_lossy()?[..]);
}
}
p.push(&dir);
}

p.push(
&dwarf
.attr_string(unit, file.path_name())?
.to_string_lossy()?[..],
);

Ok(p)
}

fn exprloc2address<R: gimli::read::Reader<Offset = usize>>(
encoding: gimli::Encoding,
data: &gimli::Expression<R>,
) -> Result<u64, anyhow::Error> {
let mut pc = data.0.clone();
while pc.len() != 0 {
if let Ok(gimli::Operation::Address { address }) =
gimli::Operation::parse(&mut pc, encoding)
{
return Ok(address);
}
}

Err(anyhow!("`Operation::Address` not found"))
}
24 changes: 16 additions & 8 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ pub fn format(ts: TokenStream) -> TokenStream {
))
}

let sym = mksym(&fs, "fmt");
let sym = mksym(&fs, "fmt", false);
exprs.push(quote!(
if f.needs_tag() {
f.istr(&defmt::export::istr(#sym));
Expand Down Expand Up @@ -445,7 +445,7 @@ fn log(level: MLevel, ts: TokenStream) -> TokenStream {
Err(e) => return e.to_compile_error().into(),
};

let sym = mksym(&ls, level.as_str());
let sym = mksym(&ls, level.as_str(), true);
let logging_enabled = is_logging_enabled(level);
quote!({
if #logging_enabled {
Expand Down Expand Up @@ -515,7 +515,7 @@ pub fn winfo(ts: TokenStream) -> TokenStream {
};

let f = &write.fmt;
let sym = mksym(&ls, "info");
let sym = mksym(&ls, "info", false /* don't care */);
quote!({
match (&mut #f, defmt::export::timestamp(), #(&(#args)),*) {
(_fmt_, ts, #(#pats),*) => {
Expand Down Expand Up @@ -560,7 +560,7 @@ pub fn intern(ts: TokenStream) -> TokenStream {
.into();
}

let sym = mksym(&ls, "str");
let sym = mksym(&ls, "str", false);
quote!({
defmt::export::istr(#sym)
})
Expand Down Expand Up @@ -624,7 +624,7 @@ pub fn write(ts: TokenStream) -> TokenStream {
};

let fmt = &write.fmt;
let sym = mksym(&ls, "fmt");
let sym = mksym(&ls, "fmt", false);
quote!(match (#fmt, #(&(#args)),*) {
(ref mut _fmt_, #(#pats),*) => {
// HACK conditional should not be here; see FIXME in `format`
Expand All @@ -638,10 +638,18 @@ pub fn write(ts: TokenStream) -> TokenStream {
.into()
}

fn mksym(string: &str, section: &str) -> TokenStream2 {
fn mksym(string: &str, section: &str, is_log_statement: bool) -> TokenStream2 {
let id = format!("{:?}", Span::call_site());
let section = format!(".defmt.{}.{}", section, string);
let sym = format!("{}@{}", string, id);
// NOTE we rely on this variable name when extracting file location information from the DWARF
// without it we have no other mean to differentiate static variables produced by `info!` vs
// produced by `intern!` (or `internp`)
let varname = if is_log_statement {
format_ident!("DEFMT_LOG_STATEMENT")
} else {
format_ident!("S")
};
quote!(match () {
#[cfg(target_arch = "x86_64")]
() => {
Expand All @@ -651,8 +659,8 @@ fn mksym(string: &str, section: &str) -> TokenStream2 {
() => {
#[link_section = #section]
#[export_name = #sym]
static S: u8 = 0;
&S as *const u8 as usize
static #varname: u8 = 0;
&#varname as *const u8 as usize
}
})
}
Expand Down

0 comments on commit a62fe2f

Please sign in to comment.