]>
Commit | Line | Data |
---|---|---|
9ffffee4 FG |
1 | extern crate addr2line; |
2 | extern crate clap; | |
3 | extern crate fallible_iterator; | |
4 | extern crate gimli; | |
5 | extern crate memmap; | |
6 | extern crate object; | |
7 | extern crate typed_arena; | |
8 | ||
9 | use std::borrow::Cow; | |
10 | use std::fs::File; | |
11 | use std::io::{BufRead, Lines, StdinLock, Write}; | |
12 | use std::path::Path; | |
13 | ||
14 | use clap::{App, Arg, Values}; | |
15 | use fallible_iterator::FallibleIterator; | |
16 | use object::{Object, ObjectSection}; | |
17 | use typed_arena::Arena; | |
18 | ||
19 | use addr2line::{Context, Location}; | |
20 | ||
21 | fn parse_uint_from_hex_string(string: &str) -> u64 { | |
22 | if string.len() > 2 && string.starts_with("0x") { | |
23 | u64::from_str_radix(&string[2..], 16).expect("Failed to parse address") | |
24 | } else { | |
25 | u64::from_str_radix(string, 16).expect("Failed to parse address") | |
26 | } | |
27 | } | |
28 | ||
29 | enum Addrs<'a> { | |
30 | Args(Values<'a>), | |
31 | Stdin(Lines<StdinLock<'a>>), | |
32 | } | |
33 | ||
34 | impl<'a> Iterator for Addrs<'a> { | |
35 | type Item = u64; | |
36 | ||
37 | fn next(&mut self) -> Option<u64> { | |
38 | let text = match *self { | |
39 | Addrs::Args(ref mut vals) => vals.next().map(Cow::from), | |
40 | Addrs::Stdin(ref mut lines) => lines.next().map(Result::unwrap).map(Cow::from), | |
41 | }; | |
42 | text.as_ref() | |
43 | .map(Cow::as_ref) | |
44 | .map(parse_uint_from_hex_string) | |
45 | } | |
46 | } | |
47 | ||
48 | fn print_loc(loc: &Option<Location>, basenames: bool, llvm: bool) { | |
49 | if let Some(ref loc) = *loc { | |
50 | let file = loc.file.as_ref().unwrap(); | |
51 | let path = if basenames { | |
52 | Path::new(Path::new(file).file_name().unwrap()) | |
53 | } else { | |
54 | Path::new(file) | |
55 | }; | |
56 | print!("{}:", path.display()); | |
57 | if llvm { | |
58 | print!("{}:{}", loc.line.unwrap_or(0), loc.column.unwrap_or(0)); | |
59 | } else if let Some(line) = loc.line { | |
60 | print!("{}", line); | |
61 | } else { | |
62 | print!("?"); | |
63 | } | |
64 | println!(); | |
65 | } else if llvm { | |
66 | println!("??:0:0"); | |
67 | } else { | |
68 | println!("??:?"); | |
69 | } | |
70 | } | |
71 | ||
72 | fn print_function(name: &str, language: Option<gimli::DwLang>, demangle: bool) { | |
73 | if demangle { | |
74 | print!("{}", addr2line::demangle_auto(Cow::from(name), language)); | |
75 | } else { | |
76 | print!("{}", name); | |
77 | } | |
78 | } | |
79 | ||
80 | fn load_file_section<'input, 'arena, Endian: gimli::Endianity>( | |
81 | id: gimli::SectionId, | |
82 | file: &object::File<'input>, | |
83 | endian: Endian, | |
84 | arena_data: &'arena Arena<Cow<'input, [u8]>>, | |
85 | ) -> Result<gimli::EndianSlice<'arena, Endian>, ()> { | |
86 | // TODO: Unify with dwarfdump.rs in gimli. | |
87 | let name = id.name(); | |
88 | match file.section_by_name(name) { | |
89 | Some(section) => match section.uncompressed_data().unwrap() { | |
90 | Cow::Borrowed(b) => Ok(gimli::EndianSlice::new(b, endian)), | |
91 | Cow::Owned(b) => Ok(gimli::EndianSlice::new(arena_data.alloc(b.into()), endian)), | |
92 | }, | |
93 | None => Ok(gimli::EndianSlice::new(&[][..], endian)), | |
94 | } | |
95 | } | |
96 | ||
97 | fn main() { | |
98 | let matches = App::new("hardliner") | |
99 | .version("0.1") | |
100 | .about("A fast addr2line clone") | |
101 | .arg( | |
102 | Arg::with_name("exe") | |
103 | .short("e") | |
104 | .long("exe") | |
105 | .value_name("filename") | |
106 | .help( | |
107 | "Specify the name of the executable for which addresses should be translated.", | |
108 | ) | |
109 | .required(true), | |
110 | ) | |
111 | .arg( | |
112 | Arg::with_name("sup") | |
113 | .long("sup") | |
114 | .value_name("filename") | |
115 | .help("Path to supplementary object file."), | |
116 | ) | |
117 | .arg( | |
118 | Arg::with_name("functions") | |
119 | .short("f") | |
120 | .long("functions") | |
121 | .help("Display function names as well as file and line number information."), | |
122 | ) | |
123 | .arg( | |
124 | Arg::with_name("pretty") | |
125 | .short("p") | |
126 | .long("pretty-print") | |
127 | .help( | |
128 | "Make the output more human friendly: each location are printed on \ | |
129 | one line.", | |
130 | ), | |
131 | ) | |
132 | .arg(Arg::with_name("inlines").short("i").long("inlines").help( | |
133 | "If the address belongs to a function that was inlined, the source \ | |
134 | information for all enclosing scopes back to the first non-inlined \ | |
135 | function will also be printed.", | |
136 | )) | |
137 | .arg( | |
138 | Arg::with_name("addresses") | |
139 | .short("a") | |
140 | .long("addresses") | |
141 | .help( | |
142 | "Display the address before the function name, file and line \ | |
143 | number information.", | |
144 | ), | |
145 | ) | |
146 | .arg( | |
147 | Arg::with_name("basenames") | |
148 | .short("s") | |
149 | .long("basenames") | |
150 | .help("Display only the base of each file name."), | |
151 | ) | |
152 | .arg(Arg::with_name("demangle").short("C").long("demangle").help( | |
153 | "Demangle function names. \ | |
154 | Specifying a specific demangling style (like GNU addr2line) \ | |
155 | is not supported. (TODO)", | |
156 | )) | |
157 | .arg( | |
158 | Arg::with_name("llvm") | |
159 | .long("llvm") | |
160 | .help("Display output in the same format as llvm-symbolizer."), | |
161 | ) | |
162 | .arg( | |
163 | Arg::with_name("addrs") | |
164 | .takes_value(true) | |
165 | .multiple(true) | |
166 | .help("Addresses to use instead of reading from stdin."), | |
167 | ) | |
168 | .get_matches(); | |
169 | ||
170 | let arena_data = Arena::new(); | |
171 | ||
172 | let do_functions = matches.is_present("functions"); | |
173 | let do_inlines = matches.is_present("inlines"); | |
174 | let pretty = matches.is_present("pretty"); | |
175 | let print_addrs = matches.is_present("addresses"); | |
176 | let basenames = matches.is_present("basenames"); | |
177 | let demangle = matches.is_present("demangle"); | |
178 | let llvm = matches.is_present("llvm"); | |
179 | let path = matches.value_of("exe").unwrap(); | |
180 | ||
181 | let file = File::open(path).unwrap(); | |
182 | let map = unsafe { memmap::Mmap::map(&file).unwrap() }; | |
183 | let object = &object::File::parse(&*map).unwrap(); | |
184 | ||
185 | let endian = if object.is_little_endian() { | |
186 | gimli::RunTimeEndian::Little | |
187 | } else { | |
188 | gimli::RunTimeEndian::Big | |
189 | }; | |
190 | ||
191 | let mut load_section = |id: gimli::SectionId| -> Result<_, _> { | |
192 | load_file_section(id, object, endian, &arena_data) | |
193 | }; | |
194 | ||
195 | let sup_map; | |
196 | let sup_object = if let Some(sup_path) = matches.value_of("sup") { | |
197 | let sup_file = File::open(sup_path).unwrap(); | |
198 | sup_map = unsafe { memmap::Mmap::map(&sup_file).unwrap() }; | |
199 | Some(object::File::parse(&*sup_map).unwrap()) | |
200 | } else { | |
201 | None | |
202 | }; | |
203 | ||
204 | let symbols = object.symbol_map(); | |
205 | let mut dwarf = gimli::Dwarf::load(&mut load_section).unwrap(); | |
206 | if let Some(ref sup_object) = sup_object { | |
207 | let mut load_sup_section = |id: gimli::SectionId| -> Result<_, _> { | |
208 | load_file_section(id, sup_object, endian, &arena_data) | |
209 | }; | |
210 | dwarf.load_sup(&mut load_sup_section).unwrap(); | |
211 | } | |
212 | ||
213 | let ctx = Context::from_dwarf(dwarf).unwrap(); | |
214 | ||
215 | let stdin = std::io::stdin(); | |
216 | let addrs = matches | |
217 | .values_of("addrs") | |
218 | .map(Addrs::Args) | |
219 | .unwrap_or_else(|| Addrs::Stdin(stdin.lock().lines())); | |
220 | ||
221 | for probe in addrs { | |
222 | if print_addrs { | |
223 | if llvm { | |
224 | print!("0x{:x}", probe); | |
225 | } else { | |
226 | print!("0x{:016x}", probe); | |
227 | } | |
228 | if pretty { | |
229 | print!(": "); | |
230 | } else { | |
231 | println!(); | |
232 | } | |
233 | } | |
234 | ||
235 | if do_functions || do_inlines { | |
236 | let mut printed_anything = false; | |
237 | let mut frames = ctx.find_frames(probe).unwrap().enumerate(); | |
238 | while let Some((i, frame)) = frames.next().unwrap() { | |
239 | if pretty && i != 0 { | |
240 | print!(" (inlined by) "); | |
241 | } | |
242 | ||
243 | if do_functions { | |
244 | if let Some(func) = frame.function { | |
245 | print_function(&func.raw_name().unwrap(), func.language, demangle); | |
246 | } else if let Some(name) = symbols.get(probe).map(|x| x.name()) { | |
247 | print_function(name, None, demangle); | |
248 | } else { | |
249 | print!("??"); | |
250 | } | |
251 | ||
252 | if pretty { | |
253 | print!(" at "); | |
254 | } else { | |
255 | println!(); | |
256 | } | |
257 | } | |
258 | ||
259 | print_loc(&frame.location, basenames, llvm); | |
260 | ||
261 | printed_anything = true; | |
262 | ||
263 | if !do_inlines { | |
264 | break; | |
265 | } | |
266 | } | |
267 | ||
268 | if !printed_anything { | |
269 | if do_functions { | |
270 | if let Some(name) = symbols.get(probe).map(|x| x.name()) { | |
271 | print_function(name, None, demangle); | |
272 | } else { | |
273 | print!("??"); | |
274 | } | |
275 | ||
276 | if pretty { | |
277 | print!(" at "); | |
278 | } else { | |
279 | println!(); | |
280 | } | |
281 | } | |
282 | ||
283 | if llvm { | |
284 | println!("??:0:0"); | |
285 | } else { | |
286 | println!("??:?"); | |
287 | } | |
288 | } | |
289 | } else { | |
290 | let loc = ctx.find_location(probe).unwrap(); | |
291 | print_loc(&loc, basenames, llvm); | |
292 | } | |
293 | ||
294 | if llvm { | |
295 | println!(); | |
296 | } | |
297 | std::io::stdout().flush().unwrap(); | |
298 | } | |
299 | } |