From 1da10ce58f427016c5255a88ba78b6b6d9ecd5b1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 11 Mar 2020 13:41:07 +1100 Subject: [PATCH] Handle multiple symbol dirs for a single binary. --- README.md | 9 ++++-- src/main.rs | 78 +++++++++++++++++++++++++++++++++++++--------------- src/tests.rs | 11 +++++++- 3 files changed, 72 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 8247a70..fe14bf9 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,12 @@ must not have changed since the stack frames were produced. Otherwise, source locations in the output may be missing or incorrect. Alternatively, you can use the `-b` option to tell `fix-stacks` to read -Breakpad symbol files, as packaged by Firefox. In this case, the processed -output will contain square brackets instead of parentheses, to make it -detectable from the output that breakpad symbols were used. +Breakpad symbol files, as packaged by Firefox. The argument must contain two +paths, separated by a comma: the first path points to the Breakpad symbols +directory, the second path points to the `fileid` executable in the Firefox +objdir. In this case, the processed output will contain square brackets instead +of parentheses, to make it detectable from the output that breakpad symbols +were used. `fix-stacks` works on Linux, Windows, and Mac. diff --git a/src/main.rs b/src/main.rs index e19cd6b..9bc867c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,8 @@ use std::env; use std::fs; use std::io::{self, BufRead, Write}; use std::path::{Path, PathBuf}; +use std::process::Command; +use std::str; use symbolic_common::{Arch, Name}; use symbolic_debuginfo::{Archive, FileFormat, Function, Object, ObjectDebugSession}; use symbolic_demangle::{Demangle, DemangleFormat, DemangleOptions}; @@ -249,12 +251,18 @@ impl FileInfo { } } +/// Info provided via the `-b` flag. +struct BreakpadInfo { + syms_dir: String, + fileid_exe: String, +} + /// The top level structure that does the work. struct Fixer { re: Regex, file_infos: FxHashMap, json_mode: JsonMode, - bp_syms_dir: Option, + bp_info: Option, lb: char, rb: char, } @@ -263,10 +271,10 @@ struct Fixer { type SymFuncAddrs = FxHashMap; impl Fixer { - fn new(json_mode: JsonMode, bp_syms_dir: Option) -> Fixer { + fn new(json_mode: JsonMode, bp_info: Option) -> Fixer { // We use parentheses with native debug info, and square brackets with // Breakpad symbols. - let (lb, rb) = if bp_syms_dir == None { + let (lb, rb) = if let None = bp_info { ('(', ')') } else { ('[', ']') @@ -276,7 +284,7 @@ impl Fixer { re: Regex::new(r"^(.*#\d+: )(.+)\[(.+) \+0x([0-9A-Fa-f]+)\](.*)$").unwrap(), file_infos: FxHashMap::default(), json_mode, - bp_syms_dir, + bp_info, lb, rb, } @@ -308,10 +316,10 @@ impl Fixer { /// Read the data from `file_name` and construct a `FileInfo` that we can /// subsequently query. Return a description of the failing operation on /// error. - fn build_file_info(bin_file: &str, bp_syms_dir: &Option) -> Result { + fn build_file_info(bin_file: &str, bp_info: &Option) -> Result { // If we're using Breakpad symbols, we don't consult `bin_file`. - if let Some(syms_dir) = bp_syms_dir { - return Fixer::build_file_info_breakpad(bin_file, syms_dir); + if let Some(bp_info) = bp_info { + return Fixer::build_file_info_breakpad(bin_file, bp_info); } // Otherwise, we read `bin_file`. @@ -326,7 +334,15 @@ impl Fixer { } } - fn build_file_info_breakpad(bin_file: &str, syms_dir: &str) -> Result { + fn build_file_info_breakpad( + bin_file: &str, + bp_info: &BreakpadInfo, + ) -> Result { + let BreakpadInfo { + syms_dir, + fileid_exe, + } = bp_info; + // We must find the `.sym` file for this `bin_file`, as produced by the // Firefox build system. A running example: // - `bin_file` is `bin/libxul.so` @@ -357,10 +373,15 @@ impl Fixer { d.map_err(|_| format!("read breakpad symbols dir `{}` for", syms_bin_dir.display()))? .path() } else { - return Err(format!( - "read breakpad symbols dir `{}` (not exactly one subdir) for", - syms_bin_dir.display() - )); + // Use `fileid` to determine the right directory. + let output = Command::new(fileid_exe) + .arg(&bin_file) + .output() + .map_err(|_| format!("run `{}` for", fileid_exe))?; + let uuid = str::from_utf8(&output.stdout).unwrap().trim_end(); + let mut syms_uuid_dir = syms_bin_dir; + syms_uuid_dir.push(uuid); + syms_uuid_dir }; // `sym_file` is `syms/libxul.so//libxul.so.sym`. @@ -641,7 +662,7 @@ impl Fixer { let file_info = match self.file_infos.entry(raw_in_file_name.to_string()) { Entry::Occupied(o) => o.into_mut(), Entry::Vacant(v) => { - match Fixer::build_file_info(&raw_in_file_name, &self.bp_syms_dir) { + match Fixer::build_file_info(&raw_in_file_name, &self.bp_info) { Ok(file_info) => v.insert(file_info), Err(op) => { // Print an error message and then set up an empty @@ -713,16 +734,20 @@ r##"usage: fix-stacks [options] < input > output Post-process the stack frames produced by MozFormatCodeAddress(). options: - -h, --help Show this message and exit - -j, --json Treat input and output as JSON fragments - -b, --breakpad DIR Use breakpad symbols in directory DIR + -h, --help Show this message and exit + -j, --json Treat input and output as JSON fragments + -b, --breakpad DIR,EXE Use breakpad symbols in directory DIR, + and `fileid` EXE to choose among possibilities "##; fn main_inner() -> io::Result<()> { // Process command line arguments. The arguments are simple enough for now // that using an external crate doesn't seem worthwhile. let mut json_mode = JsonMode::No; - let mut bp_syms_dir = None; + let mut bp_info = None; + + let err = |msg| Err(io::Error::new(io::ErrorKind::Other, msg)); + let mut args = env::args().skip(1); while let Some(arg) = args.next() { if arg == "-h" || arg == "--help" { @@ -732,10 +757,19 @@ fn main_inner() -> io::Result<()> { json_mode = JsonMode::Yes; } else if arg == "-b" || arg == "--breakpad" { match args.next() { - Some(arg) if !arg.starts_with('-') => bp_syms_dir = Some(arg), + Some(arg2) => { + let v: Vec<_> = arg2.split(',').collect(); + if v.len() == 2 { + bp_info = Some(BreakpadInfo { + syms_dir: v[0].to_string(), + fileid_exe: v[1].to_string(), + }); + } else { + return err(format!("bad argument `{}` to option `{}`.", arg2, arg)); + } + } _ => { - let msg = format!("missing or bad argument to option `{}`.", arg); - return Err(io::Error::new(io::ErrorKind::Other, msg)); + return err(format!("missing argument to option `{}`.", arg)); } } } else { @@ -743,13 +777,13 @@ fn main_inner() -> io::Result<()> { "bad argument `{}`. Run `fix-stacks -h` for more information.", arg ); - return Err(io::Error::new(io::ErrorKind::Other, msg)); + return err(msg); } } let reader = io::BufReader::new(io::stdin()); - let mut fixer = Fixer::new(json_mode, bp_syms_dir); + let mut fixer = Fixer::new(json_mode, bp_info); for line in reader.lines() { writeln!(io::stdout(), "{}", fixer.fix(line.unwrap()))?; } diff --git a/src/tests.rs b/src/tests.rs index b0162ec..8d6f42f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -297,7 +297,16 @@ fn test_breakpad() { // LINE 0x11d1 line=13 file=/home/njn/moz/fix-stacks/tests/example.c // LINE 0x11db line=14 file=/home/njn/moz/fix-stacks/tests/example.c - let mut fixer = Fixer::new(JsonMode::No, Some("tests/bpsyms".to_string())); + // We can use "" for `fileid_exe` because that field is only used for + // binaries with multiple Breakpad symbol dirs, which this test doesn't + // have. + let mut fixer = Fixer::new( + JsonMode::No, + Some(BreakpadInfo { + syms_dir: "tests/bpsyms".to_string(), + fileid_exe: "".to_string(), + }), + ); // Test various addresses. let mut func = |name, addr, linenum| {