diff --git a/Cargo.lock b/Cargo.lock index 5f244b9..f6fae27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,7 +118,7 @@ dependencies = [ [[package]] name = "cpp_demangle" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -196,11 +196,12 @@ name = "fix-stacks" version = "0.1.0" dependencies = [ "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "goblin 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", - "symbolic-common 7.0.0 (git+https://github.com/getsentry/symbolic/?rev=d217a9340df3bbd373323b732880a95e6de353bf)", - "symbolic-debuginfo 7.0.0 (git+https://github.com/getsentry/symbolic/?rev=d217a9340df3bbd373323b732880a95e6de353bf)", - "symbolic-demangle 7.0.0 (git+https://github.com/getsentry/symbolic/?rev=d217a9340df3bbd373323b732880a95e6de353bf)", + "symbolic-common 7.0.0 (git+https://github.com/nnethercote/symbolic/?rev=7d4ca78958aab62104a9fc18bb17cdba616d7a63)", + "symbolic-debuginfo 7.0.0 (git+https://github.com/nnethercote/symbolic/?rev=7d4ca78958aab62104a9fc18bb17cdba616d7a63)", + "symbolic-demangle 7.0.0 (git+https://github.com/nnethercote/symbolic/?rev=7d4ca78958aab62104a9fc18bb17cdba616d7a63)", ] [[package]] @@ -253,12 +254,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "goblin" -version = "0.0.23" +version = "0.1.1" +source = "git+https://github.com/jan-auer/goblin?rev=ee8b997792d37cfb35f8656c028543797425137e#ee8b997792d37cfb35f8656c028543797425137e" +dependencies = [ + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "scroll 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "goblin" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "scroll 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -375,7 +386,7 @@ dependencies = [ [[package]] name = "pdb" version = "0.5.0" -source = "git+https://github.com/jan-auer/pdb?rev=79112ae2fd208fcb5c8e565dd148f4b55c3e58e3#79112ae2fd208fcb5c8e565dd148f4b55c3e58e3" +source = "git+https://github.com/jan-auer/pdb?rev=6518a17aff69ed26375cf3358632abbd32073931#6518a17aff69ed26375cf3358632abbd32073931" dependencies = [ "fallible-iterator 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -620,6 +631,14 @@ dependencies = [ "scroll_derive 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "scroll" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "scroll_derive 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "scroll_derive" version = "0.9.5" @@ -630,6 +649,16 @@ dependencies = [ "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "scroll_derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "semver" version = "0.9.0" @@ -698,7 +727,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "symbolic-common" version = "7.0.0" -source = "git+https://github.com/getsentry/symbolic/?rev=d217a9340df3bbd373323b732880a95e6de353bf#d217a9340df3bbd373323b732880a95e6de353bf" +source = "git+https://github.com/nnethercote/symbolic/?rev=7d4ca78958aab62104a9fc18bb17cdba616d7a63#7d4ca78958aab62104a9fc18bb17cdba616d7a63" dependencies = [ "debugid 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -710,38 +739,38 @@ dependencies = [ [[package]] name = "symbolic-debuginfo" version = "7.0.0" -source = "git+https://github.com/getsentry/symbolic/?rev=d217a9340df3bbd373323b732880a95e6de353bf#d217a9340df3bbd373323b732880a95e6de353bf" +source = "git+https://github.com/nnethercote/symbolic/?rev=7d4ca78958aab62104a9fc18bb17cdba616d7a63#7d4ca78958aab62104a9fc18bb17cdba616d7a63" dependencies = [ "dmsort 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", "gimli 0.19.0 (git+https://github.com/jan-auer/gimli?rev=bf8ea0f0079505681c360d3a55d0ac1e9b498df3)", - "goblin 0.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "goblin 0.1.1 (git+https://github.com/jan-auer/goblin?rev=ee8b997792d37cfb35f8656c028543797425137e)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pdb 0.5.0 (git+https://github.com/jan-auer/pdb?rev=79112ae2fd208fcb5c8e565dd148f4b55c3e58e3)", + "pdb 0.5.0 (git+https://github.com/jan-auer/pdb?rev=6518a17aff69ed26375cf3358632abbd32073931)", "pest 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", - "symbolic-common 7.0.0 (git+https://github.com/getsentry/symbolic/?rev=d217a9340df3bbd373323b732880a95e6de353bf)", + "symbolic-common 7.0.0 (git+https://github.com/nnethercote/symbolic/?rev=7d4ca78958aab62104a9fc18bb17cdba616d7a63)", "zip 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "symbolic-demangle" version = "7.0.0" -source = "git+https://github.com/getsentry/symbolic/?rev=d217a9340df3bbd373323b732880a95e6de353bf#d217a9340df3bbd373323b732880a95e6de353bf" +source = "git+https://github.com/nnethercote/symbolic/?rev=7d4ca78958aab62104a9fc18bb17cdba616d7a63#7d4ca78958aab62104a9fc18bb17cdba616d7a63" dependencies = [ "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", - "cpp_demangle 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_demangle 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "msvc-demangler 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "symbolic-common 7.0.0 (git+https://github.com/getsentry/symbolic/?rev=d217a9340df3bbd373323b732880a95e6de353bf)", + "symbolic-common 7.0.0 (git+https://github.com/nnethercote/symbolic/?rev=7d4ca78958aab62104a9fc18bb17cdba616d7a63)", ] [[package]] @@ -866,7 +895,7 @@ dependencies = [ "checksum cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)" = "aa87058dce70a3ff5621797f1506cb837edd02ac4c0ae642b4542dce802908b8" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum cpp_demangle 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1d355451c6fb0dd4142db91e8dbd020d0d5466393957e771032bb9bf779504b4" +"checksum cpp_demangle 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4115af6f575a7bc82c613e9e0ed7cc36a5e4fc3a8b54920dc0c820823a31a0d6" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum debugid 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "751dad1347b163aa77262232129c7ac46e2810485c9b095ac9f7caf200e97df4" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" @@ -882,7 +911,8 @@ dependencies = [ "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum gimli 0.19.0 (git+https://github.com/jan-auer/gimli?rev=bf8ea0f0079505681c360d3a55d0ac1e9b498df3)" = "" "checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -"checksum goblin 0.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "ac56b4753b6b8c2e052ca30717e5a09acf1b02a2c1681bf3d883bd660e5d22bd" +"checksum goblin 0.1.1 (git+https://github.com/jan-auer/goblin?rev=ee8b997792d37cfb35f8656c028543797425137e)" = "" +"checksum goblin 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88a79ef1f0dad46fd78075b6f80f92d97710eddf87b3e18a15a66761e8942672" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" @@ -899,7 +929,7 @@ dependencies = [ "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" "checksum parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7" "checksum parking_lot_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb88cb1cb3790baa6776844f968fea3be44956cf184fa1be5a03341f5491278c" -"checksum pdb 0.5.0 (git+https://github.com/jan-auer/pdb?rev=79112ae2fd208fcb5c8e565dd148f4b55c3e58e3)" = "" +"checksum pdb 0.5.0 (git+https://github.com/jan-auer/pdb?rev=6518a17aff69ed26375cf3358632abbd32073931)" = "" "checksum pest 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e4fb201c5c22a55d8b24fef95f78be52738e5e1361129be1b5e862ecdb6894a" "checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" "checksum pest_generator 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9fcf299b5712d06ee128a556c94709aaa04512c4dffb8ead07c5c998447fc0" @@ -928,7 +958,9 @@ dependencies = [ "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" +"checksum scroll 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "abb2332cb595d33f7edd5700f4cbf94892e680c7f0ae56adab58a35190b66cb1" "checksum scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f84d114ef17fd144153d608fba7c446b0145d038985e7a8cc5d08bb0ce20383" +"checksum scroll_derive 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8584eea9b9ff42825b46faf46a8c24d2cff13ec152fa2a50df788b87c07ee28" "checksum scroll_derive 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1aa96c45e7f5a91cb7fabe7b279f02fea7126239fc40b732316e8b6a2d0fcb" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" @@ -938,9 +970,9 @@ dependencies = [ "checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" "checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" -"checksum symbolic-common 7.0.0 (git+https://github.com/getsentry/symbolic/?rev=d217a9340df3bbd373323b732880a95e6de353bf)" = "" -"checksum symbolic-debuginfo 7.0.0 (git+https://github.com/getsentry/symbolic/?rev=d217a9340df3bbd373323b732880a95e6de353bf)" = "" -"checksum symbolic-demangle 7.0.0 (git+https://github.com/getsentry/symbolic/?rev=d217a9340df3bbd373323b732880a95e6de353bf)" = "" +"checksum symbolic-common 7.0.0 (git+https://github.com/nnethercote/symbolic/?rev=7d4ca78958aab62104a9fc18bb17cdba616d7a63)" = "" +"checksum symbolic-debuginfo 7.0.0 (git+https://github.com/nnethercote/symbolic/?rev=7d4ca78958aab62104a9fc18bb17cdba616d7a63)" = "" +"checksum symbolic-demangle 7.0.0 (git+https://github.com/nnethercote/symbolic/?rev=7d4ca78958aab62104a9fc18bb17cdba616d7a63)" = "" "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" "checksum syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92" "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" diff --git a/Cargo.toml b/Cargo.toml index 4d7c8e6..de0d4e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ license = "MIT/Apache-2.0" [dependencies] fxhash = "0.2.1" +goblin = "0.1.2" regex = "1.0" serde_json = "1.0" @@ -14,6 +15,12 @@ serde_json = "1.0" # writing, 6.1.4 is the latest version of `symbolic` on crates.io, but this dev # version of 7.0.0 fixes some API botches (`ObjectDebugSession::functions()` # being private). Once 7.0.0 is released, we can switch to it. -symbolic-common = { git = "https://github.com/getsentry/symbolic/", rev = "d217a9340df3bbd373323b732880a95e6de353bf" } -symbolic-debuginfo = { git = "https://github.com/getsentry/symbolic/", rev = "d217a9340df3bbd373323b732880a95e6de353bf" } -symbolic-demangle = { git = "https://github.com/getsentry/symbolic/", rev = "d217a9340df3bbd373323b732880a95e6de353bf" } +# +# FIXME(https://github.com/mozilla/fix-stacks/issues/11): if/when my +# symbolic-debuginfo changes land +# (https://github.com/getsentry/symbolic/pull/173), change these to point again +# to the `getsentry` repo. +# +symbolic-common = { git = "https://github.com/nnethercote/symbolic/", rev = "7d4ca78958aab62104a9fc18bb17cdba616d7a63" } +symbolic-debuginfo = { git = "https://github.com/nnethercote/symbolic/", rev = "7d4ca78958aab62104a9fc18bb17cdba616d7a63" } +symbolic-demangle = { git = "https://github.com/nnethercote/symbolic/", rev = "7d4ca78958aab62104a9fc18bb17cdba616d7a63" } diff --git a/README.md b/README.md index 761a564..a006e49 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ This program post-processes ("fixes") the stack frames produced by `MozFormatCodeAddress()`, which often lack one or more of: function name, file -name, line number. It relies on the `symbolic` crate to read debug info from -files. +name, line number. It relies on the `symbolic` and `goblin` crates to read +debug info from files. It reads from standard input and writes to standard output. Lines matching the special stack frame format are modified appropriately. For example, a line @@ -24,10 +24,9 @@ the stack frames and the build files. Furthermore, the build files must not have changed since the stack frames were produced. Otherwise, source locations in the output may be missing or incorrect. -# Shortcomings +`fix-stacks` works on Linux, Windows, and Mac. -`fix-stacks` works on Linux and Windows. We aim to eventually support -[Mac](https://github.com/mozilla/fix-stacks/issues/3) and possibly Android. +# Shortcomings On Linux, use with debuginfo sections in separate files is untested and probably does not work. diff --git a/src/main.rs b/src/main.rs index fd64fa0..7e90060 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,14 +3,15 @@ // http://opensource.org/licenses/MIT>, at your option. This file may not be // copied, modified, or distributed except according to those terms. -use fxhash::FxHashMap; +use fxhash::{FxHashMap, FxHashSet}; +use goblin::{archive, mach}; use regex::Regex; use std::collections::hash_map::Entry; use std::env; use std::fs; use std::io::{self, BufRead, Write}; -use symbolic_common::Name; -use symbolic_debuginfo::{FileFormat, Function, Object, ObjectDebugSession}; +use symbolic_common::{Arch, Name}; +use symbolic_debuginfo::{Archive, FileFormat, Function, Object, ObjectDebugSession}; use symbolic_demangle::{Demangle, DemangleFormat, DemangleOptions}; #[cfg(test)] @@ -57,6 +58,18 @@ enum JsonEscaping { Yes, } +fn format_address(address: u64, offset: i64) -> String { + if offset == 0 { + format!("0x{:x}", address) + } else { + format!( + "0x{:x} -> 0x{:x}", + address, + (address as i64 + offset) as u64 + ) + } +} + /// Debug info for a single line. struct LineInfo { address: u64, @@ -67,17 +80,17 @@ struct LineInfo { } impl LineInfo { - fn new(interner: &mut Interner, line: symbolic_debuginfo::LineInfo) -> LineInfo { + fn new(interner: &mut Interner, line: symbolic_debuginfo::LineInfo, offset: i64) -> LineInfo { if PRINT_FUNCS_AND_LINES { eprintln!( - "LINE 0x{:x} line={} file={}", - line.address, + "LINE {} line={} file={}", + format_address(line.address, offset), line.line, line.file.path_str() ); } LineInfo { - address: line.address, + address: (line.address as i64 + offset) as u64, line: line.line, path: interner.intern(line.file.path_str()), } @@ -98,23 +111,23 @@ struct FuncInfo { } impl FuncInfo { - fn new(interner: &mut Interner, function: Function) -> FuncInfo { + fn new(interner: &mut Interner, function: Function, offset: i64) -> FuncInfo { if PRINT_FUNCS_AND_LINES { eprintln!( - "FUNC 0x{:x} size={} func={}", - function.address, + "FUNC {} size={} func={}", + format_address(function.address, offset), function.size, function.name.as_str() ); } FuncInfo { - address: function.address, + address: (function.address as i64 + offset) as u64, size: function.size, mangled_name: function.name.as_str().to_string(), line_infos: function .lines .into_iter() - .map(|line| LineInfo::new(interner, line)) + .map(|line| LineInfo::new(interner, line, offset)) .collect(), } } @@ -146,10 +159,12 @@ impl FuncInfo { } /// Debug info for a single file. +#[derive(Default)] struct FileInfo { + interner: Interner, + /// The `FuncInfo`s are sorted by `address`. func_infos: Vec, - interner: Interner, } impl FileInfo { @@ -160,18 +175,60 @@ impl FileInfo { .functions() .filter_map(|function| { let function = function.ok()?; - Some(FuncInfo::new(&mut interner, function)) + Some(FuncInfo::new(&mut interner, function, 0)) }) .collect(); func_infos.sort_unstable_by_key(|func_info| func_info.address); func_infos.dedup_by_key(|func_info| func_info.address); + FileInfo { + interner, + func_infos, + } + } + + /// Add the debug info from `debug_session` for functions in `filename` + /// to that already gathered in `interner` and `func_infos`, but only for + /// functions present in `sym_func_addrs`. + fn add( + sym_func_addrs: &SymFuncAddrs, + file_name: &str, + debug_session: ObjectDebugSession, + interner: &mut Interner, + func_infos: &mut Vec, + ) { + // Build the `FileInfo` from the debug session. + func_infos.extend(debug_session.functions().filter_map(|function| { + let function = function.ok()?; + + // If a function appears in the debug info but was not seen in + // the parent binary's symbol table, just ignore it. This is + // common, perhaps due to inlining (i.e. inlined functions don't + // end up with their own symbols in the binary). + // + // Otherwise, we know the function's address in the parent binary's + // symbol table. Adjust all the addresses from the debug info to + // match that address. + let sym_func_key = Fixer::sym_func_key(file_name, function.name.as_str()); + let sym_func_addr = sym_func_addrs.get(&sym_func_key)?; + let offset = *sym_func_addr as i64 - function.address as i64; + Some(FuncInfo::new(interner, function, offset)) + })); + } + + /// Finish constructing a `FileInfo` that has been built up using + /// `Fixer::add`. + fn finish(interner: Interner, mut func_infos: Vec) -> FileInfo { + func_infos.sort_unstable_by_key(|func_info| func_info.address); + func_infos.dedup_by_key(|func_info| func_info.address); + FileInfo { func_infos, interner, } } + /// Get the `FuncInfo` for an address, if there is one. fn func_info(&self, address: u64) -> Option<&FuncInfo> { match self .func_infos @@ -198,6 +255,9 @@ struct Fixer { json_escaping: JsonEscaping, } +/// Records address of functions from a symbol table. +type SymFuncAddrs = FxHashMap; + impl Fixer { fn new(json_escaping: JsonEscaping) -> Fixer { Fixer { @@ -220,38 +280,255 @@ impl Fixer { /// subsequently query. Return a description of the failing operation on /// error. fn build_file_info(file_name: &str) -> Result { - let msg = |op: &str| format!("Unable to {} `{}`", op, file_name); - - // Read the file. - let mut data = fs::read(file_name).map_err(|_| msg("read"))?; - - // On some platforms we have to get the debug info from another file. - // Get the name of that file, if there is one. - let file_name2 = match Object::peek(&data) { - FileFormat::Pe => { - let pe_object = Object::parse(&data).map_err(|_| msg("parse"))?; - if let Object::Pe(pe) = pe_object { - // PE files should contain a pointer to a PDB file. - let pdb_file_name = pe.debug_file_name().ok_or_else(|| msg("find PDB for"))?; - Some(pdb_file_name.to_string()) + let data = fs::read(file_name).map_err(|_| "read")?; + let file_format = Archive::peek(&data); + match file_format { + FileFormat::Elf => Fixer::build_file_info_direct(&data), + FileFormat::Pe => Fixer::build_file_info_pe(&data), + FileFormat::Pdb => Fixer::build_file_info_direct(&data), + FileFormat::MachO => Fixer::build_file_info_macho(&data), + _ => Err(format!("parse {} format file", file_format)), + } + } + + // "Direct" means that the debug info is within `data`, as opposed to being + // in another file that `data` refers to. + fn build_file_info_direct(data: &[u8]) -> Result { + let object = Object::parse(&data).map_err(|_| "parse")?; + let debug_session = object.debug_session().map_err(|_| "read debug info from")?; + Ok(FileInfo::new(debug_session)) + } + + fn build_file_info_pe(data: &[u8]) -> Result { + // For PEs we get the debug info from a PDB file. + let pe_object = Object::parse(&data).map_err(|_| "parse")?; + let pe = match pe_object { + Object::Pe(pe) => pe, + _ => unreachable!(), + }; + let pdb_file_name = pe.debug_file_name().ok_or("find debug info file for")?; + let data = fs::read(pdb_file_name.to_string()) + .map_err(|_| format!("read debug info file `{}` for", pdb_file_name))?; + Fixer::build_file_info_direct(&data) + } + + fn build_file_info_macho(data: &[u8]) -> Result { + // On Mac, debug info is typically stored in `.dSYM` directories. But + // they aren't normally built for Firefox because doing so is slow. + // Instead, we read the symbol table of the given file, which has + // pointers to all the object files from which it was constructed. We + // then obtain the debug info from those object files (some of which + // are embedded within `.a` files), and adjust the addresses from the + // debug info appropriately. All this requires the object files to + // still be present, and matches what `atos` does. + // + // Doing all this requires a lower level of processing than what the + // `symbolic` crate provides, so instead we use the `goblin` crate. + // + // We stop if any errors are encountered. The code could be made more + // robust in the face of errors if necessary. + + let macho = Fixer::macho(&data)?; + let sym_func_addrs = Fixer::sym_func_addrs(&macho)?; + + // Iterate again through the symbol table, reading every object file + // that is referenced, and adjusting the addresses in those files using + // the function addresses obtained above. + let mut seen_archives = FxHashSet::default(); + let mut func_infos = vec![]; + let mut interner = Interner::default(); + for sym in macho.symbols() { + let (oso_name, nlist) = sym.map_err(|_| "read symbol table from")?; + if nlist.is_stab() && nlist.n_type == mach::symbols::N_OSO { + if let Some(ar_file_name) = Fixer::is_within_archive(oso_name) { + // It's an archive entry, e.g. "libgkrust.a(foo.o)". Read + // every entry in archive, if we haven't already done so. + if seen_archives.insert(ar_file_name) { + let ar_data = fs::read(ar_file_name) + .map_err(|_| format!("read ar `{}` referenced by", ar_file_name))?; + let ar = archive::Archive::parse(&ar_data) + .map_err(|_| format!("parse ar `{}` referenced by", ar_file_name))?; + + for (name, _, _) in ar.summarize() { + let data = ar.extract(name, &ar_data).map_err(|_| { + format!("read an entry in ar `{}` referenced by", ar_file_name) + })?; + Fixer::do_macho_oso( + &sym_func_addrs, + ar_file_name, + data, + &mut interner, + &mut func_infos, + )?; + } + } } else { - panic!(); // Impossible: peek() said it was a PE object. + // It's a normal object file. Read it. + let data = fs::read(oso_name) + .map_err(|_| format!("read `{}` referenced by", oso_name))?; + Fixer::do_macho_oso( + &sym_func_addrs, + oso_name, + &data, + &mut interner, + &mut func_infos, + )?; } } - _ => None, - }; - if let Some(file_name2) = file_name2 { - data = fs::read(&file_name2) - .map_err(|_| msg(&format!("read debug info file `{}` for", file_name2)))?; } - // Get the debug session from the file data. - let object = Object::parse(&data).map_err(|_| msg("parse"))?; + Ok(FileInfo::finish(interner, func_infos)) + } + + fn macho(data: &[u8]) -> Result { + let mach = mach::Mach::parse(&data).map_err(|_| "parse (with goblin)")?; + match mach { + mach::Mach::Binary(macho) => Ok(macho), + mach::Mach::Fat(multi_arch) => { + // Get the x86-64 object from the fat binary. (On Mac, Firefox + // is only available on x86-64.) + let macho = multi_arch.into_iter().find(|macho| { + if let Ok(macho) = macho { + if macho.header.cputype() == mach::constants::cputype::CPU_TYPE_X86_64 { + return true; + } + } + false + }); + + // This chaining is necessary because `MachOIterator::Item` is + // not `MachO` but `Result`. We don't distinguish + // between the "couldn't find the x86-64 code" case and the + // "found it, but it had an error" case. + let msg = "find x86-64 code in the fat binary"; + macho.ok_or(msg)?.map_err(|_| msg.to_string()) + } + } + } + + /// Iterate through the symbol table, getting the address of every function + /// in the file. + fn sym_func_addrs(macho: &mach::MachO) -> Result { + let object_load_address = Fixer::object_load_address(macho); + let mut sym_func_addrs = FxHashMap::default(); + let mut curr_oso_name = String::new(); + for sym in macho.symbols() { + let (name, nlist) = sym.map_err(|_| "read symbol table from")?; + if !nlist.is_stab() { + continue; + } + + if nlist.n_type == mach::symbols::N_OSO { + // Record this reference to an object file (or archive). + curr_oso_name = if let Some(ar_file_name) = Fixer::is_within_archive(name) { + // We have to strip the archive suffix, because the suffix + // in the symbol table often disagrees with the suffix in + // the debug info. E.g. + // - symbol table: `libjs_static.a(Unified_cpp_js_src9.o)` + // - debug info: `libjs_static.a(RegExp.o)` + // + // It's unclear why this occurs, though it doesn't seem to + // matter much, though see the comment about duplicates + // below. + ar_file_name.to_string() + } else { + name.to_string() + } + } else if nlist.n_type == mach::symbols::N_FUN + && nlist.n_sect != mach::symbols::NO_SECT as usize + { + let name = &name[1..]; // Trim the leading underscore. + let address = nlist.n_value - object_load_address; + + let sym_func_key = Fixer::sym_func_key(&curr_oso_name, name); + + // There can be duplicates, in which case the last one "wins". + // This seems to only occur due to the removal of archive + // suffixes above. In practice it doesn't seem to matter. + sym_func_addrs.insert(sym_func_key, address); + } + } + + Ok(sym_func_addrs) + } + + // This is based on `symbolic_debuginfo::macho:MachObject::load_address`. + // We need to define it because `goblin` doesn't have an equivalent, and we + // use `goblin` rather than `symbolic_debuginfo` for Mach-O binaries. + fn object_load_address(macho: &mach::MachO) -> u64 { + for seg in macho.segments.iter() { + if let Ok(name) = seg.name() { + if name == "__TEXT" { + return seg.vmaddr; + } + } + } + 0 + } + + /// Construct a key for the `sym_func_addrs` hash map. + fn sym_func_key(file_name: &str, func_name: &str) -> String { + format!("{}:{}", file_name, func_name) + } + + /// Is this filename within an archive? E.g. `libfoo.a(bar.o)` means that + /// `bar.o` is within the archive `libfoo.a`. If so, return the archive + /// name. + fn is_within_archive(file_name: &str) -> Option<&str> { + if let (Some(index), true) = (file_name.find(".a("), file_name.ends_with(")")) { + let ar_file_name = &file_name[..index + 2]; + Some(ar_file_name) + } else { + None + } + } + + /// Read the debug info from a file referenced by an OSO entry in a Macho-O + /// symbol table. + fn do_macho_oso( + sym_func_addrs: &SymFuncAddrs, + file_name: &str, + data: &[u8], + interner: &mut Interner, + func_infos: &mut Vec, + ) -> Result<(), String> { + // Although we use `goblin` to iterate through the symbol + // table, we use `symbolic` to read the debug info from the + // object/archive, because it's easier to use. + let archive = Archive::parse(&data) + .map_err(|e| format!("({:?}) parse `{}` referenced by", e, file_name))?; + + // Get the x86-64 object from the archive, which might be a fat binary. + // (On Mac, Firefox is only available on x86-64.) + let mut x86_64_object = None; + for object in archive.objects() { + let object = object + .map_err(|_| format!("parse fat binary entry in `{}` referenced by", file_name))?; + if object.arch() == Arch::Amd64 { + x86_64_object = Some(object); + break; + } + } + + let object = x86_64_object.ok_or_else(|| { + format!( + "find x86-64 code in the fat binary `{}` referenced by", + file_name + ) + })?; let debug_session = object .debug_session() - .map_err(|_| msg("read debug info from"))?; + .map_err(|_| format!("read debug info from `{}` referenced by", file_name))?; - Ok(FileInfo::new(debug_session)) + FileInfo::add( + &sym_func_addrs, + file_name, + debug_session, + interner, + func_infos, + ); + + Ok(()) } /// Fix stack frames within `line` as necessary. Prints any errors to stderr. @@ -276,9 +553,16 @@ impl Fixer { Entry::Occupied(o) => o.into_mut(), Entry::Vacant(v) => match Fixer::build_file_info(file_name) { Ok(file_info) => v.insert(file_info), - Err(msg) => { - eprintln!("{}", msg); - return line; + Err(op) => { + // Print an error message and then set up an empty + // `FileInfo` for this file, for two reasons. + // - If an invalid file is mentioned multiple times in the + // input, an error message will be issued only on the + // first occurrence. + // - The line will still receive some transformation, using + // the "no symbols or debug info" case below. + eprintln!("fix-stacks error: failed to {} `{}`", op, file_name); + v.insert(FileInfo::default()) } }, }; diff --git a/src/tests.rs b/src/tests.rs index 59dad6e..195a573 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -33,7 +33,7 @@ fn test_linux() { let mut fixer = Fixer::new(JsonEscaping::No); - // Test various addresses within `main`. + // Test various addresses. let mut func = |name, addr, linenum| { let line = format!("#00: ???[tests/example-linux +0x{:x}]", addr); let line = fixer.fix(line); @@ -111,8 +111,8 @@ fn test_windows() { let mut fixer = Fixer::new(JsonEscaping::Yes); - // Test various addresses within `main` using `example-windows`, which - // redirects to `example-windows.pdb`. + // Test various addresses using `example-windows`, which redirects to + // `example-windows.pdb`. let mut func = |name, addr, linenum| { let line = format!("#00: ???[tests/example-windows +0x{:x}]", addr); let line = fixer.fix(line); @@ -158,6 +158,109 @@ fn test_windows() { outside(0xfffffff); // A very high address. } +#[test] +fn test_mac() { + // The debug info within `mac-multi` is as follows. (See `tests/README.md` + // for details on how these lines were generated.) + // + // FUNC 0xd70 size=54 func=main + // LINE 0xd70 line=17 file=/Users/njn/moz/fix-stacks/tests/mac-normal.c + // LINE 0xd7f line=18 file=/Users/njn/moz/fix-stacks/tests/mac-normal.c + // LINE 0xd86 line=19 file=/Users/njn/moz/fix-stacks/tests/mac-normal.c + // LINE 0xd8f line=20 file=/Users/njn/moz/fix-stacks/tests/mac-normal.c + // LINE 0xd98 line=21 file=/Users/njn/moz/fix-stacks/tests/mac-normal.c + // LINE 0xd9d line=22 file=/Users/njn/moz/fix-stacks/tests/mac-normal.c + // + // FUNC 0xdb0 size=31 func=duplicate + // LINE 0xdb0 line=10 file=/Users/njn/moz/fix-stacks/tests/mac-normal.c + // LINE 0xdb8 line=11 file=/Users/njn/moz/fix-stacks/tests/mac-normal.c + // LINE 0xdc9 line=12 file=/Users/njn/moz/fix-stacks/tests/mac-normal.c + // + // FUNC 0xdd0 size=37 func=fat_B + // LINE 0xdd0 line=19 file=/Users/njn/moz/fix-stacks/tests/mac-fat.c + // LINE 0xddc line=20 file=/Users/njn/moz/fix-stacks/tests/mac-fat.c + // LINE 0xdef line=21 file=/Users/njn/moz/fix-stacks/tests/mac-fat.c + // + // FUNC 0xe00 size=34 func=fat_A + // LINE 0xe00 line=13 file=/Users/njn/moz/fix-stacks/tests/mac-fat.c + // LINE 0xe0b line=14 file=/Users/njn/moz/fix-stacks/tests/mac-fat.c + // LINE 0xe14 line=15 file=/Users/njn/moz/fix-stacks/tests/mac-fat.c + // LINE 0xe19 line=16 file=/Users/njn/moz/fix-stacks/tests/mac-fat.c + // + // FUNC 0xe30 size=31 func=duplicate + // LINE 0xe30 line=9 file=/Users/njn/moz/fix-stacks/tests/mac-fat.c + // LINE 0xe38 line=10 file=/Users/njn/moz/fix-stacks/tests/mac-fat.c + // LINE 0xe49 line=11 file=/Users/njn/moz/fix-stacks/tests/mac-fat.c + // + // FUNC 0xe50 size=37 func=lib1_B + // LINE 0xe50 line=19 file=/Users/njn/moz/fix-stacks/tests/mac-lib1.c + // LINE 0xe5c line=20 file=/Users/njn/moz/fix-stacks/tests/mac-lib1.c + // LINE 0xe6f line=21 file=/Users/njn/moz/fix-stacks/tests/mac-lib1.c + // + // FUNC 0xe80 size=34 func=lib1_A + // LINE 0xe80 line=13 file=/Users/njn/moz/fix-stacks/tests/mac-lib1.c + // LINE 0xe8b line=14 file=/Users/njn/moz/fix-stacks/tests/mac-lib1.c + // LINE 0xe94 line=15 file=/Users/njn/moz/fix-stacks/tests/mac-lib1.c + // LINE 0xe99 line=16 file=/Users/njn/moz/fix-stacks/tests/mac-lib1.c + // + // // Note that these ones are wrong, and should start at 0xeb0. This is + // // due to the unavoidable archive suffix stripping, see comments in + // // `main.rs` for details. + // FUNC 0xf30 size=31 func=duplicate + // LINE 0xf30 line=9 file=/Users/njn/moz/fix-stacks/tests/mac-lib1.c + // LINE 0xf38 line=10 file=/Users/njn/moz/fix-stacks/tests/mac-lib1.c + // LINE 0xf49 line=11 file=/Users/njn/moz/fix-stacks/tests/mac-lib1.c + // + // FUNC 0xed0 size=37 func=lib2_B + // LINE 0xed0 line=19 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c + // LINE 0xedc line=20 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c + // LINE 0xeef line=21 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c + // + // FUNC 0xf00 size=39 func=lib2_A + // LINE 0xf00 line=13 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c + // LINE 0xf0b line=14 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c + // LINE 0xf19 line=15 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c + // LINE 0xf1e line=16 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c + // + // FUNC 0xf30 size=31 func=duplicate + // LINE 0xf30 line=9 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c + // LINE 0xf38 line=10 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c + // LINE 0xf49 line=11 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c + + let mut fixer = Fixer::new(JsonEscaping::No); + + // Test addresses from all the object files that `mac-multi` references. + let mut func = |name, addr, full_path, locn| { + let line = format!("#00: ???[tests/mac-multi +0x{:x}]", addr); + let line = fixer.fix(line); + let path = if full_path { + "/Users/njn/moz/fix-stacks/tests/" + } else { + "tests/" + }; + assert_eq!(line, format!("#00: {} ({}{})", name, path, locn)); + }; + + func("main", 0xd70, true, "mac-normal.c:17"); + func("duplicate", 0xdb3, true, "mac-normal.c:10"); + + func("fat_B", 0xddc, true, "mac-fat.c:20"); + func("fat_A", 0xe19, true, "mac-fat.c:16"); + func("duplicate", 0xe4e, true, "mac-fat.c:11"); + + func("lib1_B", 0xe50, true, "mac-lib1.c:19"); + func("lib1_A", 0xe95, true, "mac-lib1.c:15"); + // This should be `duplicate` in `mac-lib1.c`. It's wrong due to the + // archive suffix stripping mentioned above. + func("???", 0xeaa, false, "mac-multi"); + + func("lib2_B", 0xedc, true, "mac-lib2.c:20"); + func("lib2_A", 0xf1e, true, "mac-lib2.c:16"); + // This should be `mac-lib2.c:10`. It's wrong due to the archive suffix + // stripping mentioned above. + func("duplicate", 0xf38, true, "mac-lib1.c:10"); +} + #[test] fn test_regex() { let mut fixer = Fixer::new(JsonEscaping::No); @@ -176,8 +279,8 @@ fn test_regex() { // An error message is also printed to stderr for file errors, but we don't // test for that. - unchanged("#00: ???[tests/no-such-file +0x43a0]"); // No such file. - unchanged("#00: ???[src/main.rs +0x43a0]"); // File exists, but has wrong format. + unchanged("#00: ??? (tests/no-such-file)"); // No such file. + unchanged("#00: ??? (src/main.rs)"); // File exists, but has wrong format. // Test various different changed line forms that do match the regex. let mut changed = |line1: &str, line2_expected| { diff --git a/tests/README.md b/tests/README.md index 193b139..0c94fb0 100644 --- a/tests/README.md +++ b/tests/README.md @@ -22,19 +22,56 @@ To allow this requires the following. ## Generating inputs Debug info is very complicated and hard to write by hand. Therefore, the tests -use executables and other data files created by compilers as inputs. - -The primary source code file used for generating these files is -`tests/example.c`. The following files were generated from it. -- `example-linux`: produced on an Ubuntu 19.04 box by clang 8.0 with the - command `clang -g example.c -o example-linux`. -- `example-windows` and `example-windows.pdb`: produced on a Windows 10 laptop - by clang 9.0 with the command `clang -g example.c -o example-windows`. - `example-windows` was then hex-edited to change the PDB reference from the - absolute path `c:\Users\njn\moz\fix-stacks\tests\example-windows.pdb` to the - relative path `tests/////////////////////////////example-windows.pdb`. (The - use of many redundant forward slashes is a hack to keep the path the same - length, which avoids the need for more complex changes to that file.) +use executables and other data files created by compilers as inputs. These were +all generated within the `test` directory in the following ways. + +### Linux + +`example-linux` was produced on an Ubuntu 19.04 box by clang 8.0 with this +command: +``` +clang -g example.c -o example-linux +``` + +### Windows + +`example-windows` and `example-windows.pdb` were produced on a Windows 10 laptop + by clang 9.0 with this command: +``` +clang -g example.c -o example-windows +``` +`example-windows` was then hex-edited to change the PDB reference from the +absolute path `c:\Users\njn\moz\fix-stacks\tests\example-windows.pdb` to the +relative path `tests/////////////////////////////example-windows.pdb`. (The use +of many redundant forward slashes is a hack to keep the path the same length, +which avoids the need for more complex changes to that file.) + +### Mac + +The Mac tests are more complex because `fix-stacks`'s code for handling Mach-O +binaries is more complex than other formats. + +`mac-multi` was produced on a MacBook Pro running macOS 10.14 by Apple clang +11.0 with these commands: +``` +# A normal file. +clang -c -g mac-normal.c -o mac-normal.o +# A fat binary. +clang -m32 -c -g mac-fat.c -o mac-fat-32.o +clang -m64 -c -g mac-fat.c -o mac-fat-64.o +lipo -create mac-fat-32.o mac-fat-64.o -output mac-fat.o +# A library. +clang -c -g mac-lib1.c -o mac-lib1.o +clang -c -g mac-lib2.c -o mac-lib2.o +ar -r libexample.a mac-lib1.o mac-lib2.o +# The final executable. +clang mac-normal.o mac-fat.o libexample.a -o mac-multi +``` +`mac-multi` was then hex-edited to change all the file reference from the +absolute paths such as `/Users/njn/moz/fix-stacks/tests/mac-normal.c` to the +relative paths such as `tests///////////////////////////mac-normal.c`. (The use +of many redundant forward slashes is a hack to keep the path the same length, +which avoids the need for more complex changes to that file.) ## Obtaining the debug info diff --git a/tests/libexample.a b/tests/libexample.a new file mode 100644 index 0000000..729b0a4 Binary files /dev/null and b/tests/libexample.a differ diff --git a/tests/mac-fat-32.o b/tests/mac-fat-32.o new file mode 100644 index 0000000..c7904f6 Binary files /dev/null and b/tests/mac-fat-32.o differ diff --git a/tests/mac-fat-64.o b/tests/mac-fat-64.o new file mode 100644 index 0000000..507fe19 Binary files /dev/null and b/tests/mac-fat-64.o differ diff --git a/tests/mac-fat.c b/tests/mac-fat.c new file mode 100644 index 0000000..5dac5b8 --- /dev/null +++ b/tests/mac-fat.c @@ -0,0 +1,21 @@ +// This file contains the source code for some tests. If you change this file +// you may need to regenerate some test files. (Even changing the number of +// lines in this comment would have an effect.) +// +// See `tests/README.md` for more details. + +#include + +static void duplicate() { + printf("fat duplicate"); +} + +static int fat_A(int x) { + x = x * 2; + duplicate(); + return x; +} + +void fat_B(int* x) { + *x += fat_A(*x); +} diff --git a/tests/mac-fat.o b/tests/mac-fat.o new file mode 100644 index 0000000..d915fc4 Binary files /dev/null and b/tests/mac-fat.o differ diff --git a/tests/mac-lib1.c b/tests/mac-lib1.c new file mode 100644 index 0000000..48a8101 --- /dev/null +++ b/tests/mac-lib1.c @@ -0,0 +1,21 @@ +// This file contains the source code for some tests. If you change this file +// you may need to regenerate some test files. (Even changing the number of +// lines in this comment would have an effect.) +// +// See `tests/README.md` for more details. + +#include + +static void duplicate() { + printf("lib1 duplicate"); +} + +static int lib1_A(int x) { + x = x + 2; + duplicate(); + return x; +} + +void lib1_B(int* x) { + *x += lib1_A(*x); +} diff --git a/tests/mac-lib1.o b/tests/mac-lib1.o new file mode 100644 index 0000000..48a96a0 Binary files /dev/null and b/tests/mac-lib1.o differ diff --git a/tests/mac-lib2.c b/tests/mac-lib2.c new file mode 100644 index 0000000..f482651 --- /dev/null +++ b/tests/mac-lib2.c @@ -0,0 +1,21 @@ +// This file contains the source code for some tests. If you change this file +// you may need to regenerate some test files. (Even changing the number of +// lines in this comment would have an effect.) +// +// See `tests/README.md` for more details. + +#include + +static void duplicate() { + printf("lib2 duplicate"); +} + +static int lib2_A(int x) { + x = x / 2; + duplicate(); + return x; +} + +void lib2_B(int* x) { + *x += lib2_A(*x); +} diff --git a/tests/mac-lib2.o b/tests/mac-lib2.o new file mode 100644 index 0000000..6b8dc4f Binary files /dev/null and b/tests/mac-lib2.o differ diff --git a/tests/mac-multi b/tests/mac-multi new file mode 100755 index 0000000..dff9c32 Binary files /dev/null and b/tests/mac-multi differ diff --git a/tests/mac-normal.c b/tests/mac-normal.c new file mode 100644 index 0000000..a9bbf2e --- /dev/null +++ b/tests/mac-normal.c @@ -0,0 +1,23 @@ +// This file contains the source code for some tests. If you change this file +// you may need to regenerate some test files. (Even changing the number of +// lines in this comment would have an effect.) +// +// See `tests/README.md` for more details. + + +#include + +static void duplicate() { + printf("normal duplicate"); +} + +void lib1_B(int* x); +void lib2_B(int* x); + +int main() { + int x = 0; + lib1_B(&x); + lib2_B(&x); + duplicate(); + return x; +} diff --git a/tests/mac-normal.o b/tests/mac-normal.o new file mode 100644 index 0000000..02140df Binary files /dev/null and b/tests/mac-normal.o differ