1use crate::config::{types::TraceEngine, Config};
2use crate::path_utils::{fix_unc_path, is_coverable_file_path};
3use crate::source_analysis::*;
4use crate::traces::*;
5use gimli::*;
6use object::{read::ObjectSection, Object};
7use rustc_demangle::demangle;
8use std::collections::{HashMap, HashSet};
9use std::fs::File;
10use std::io;
11use std::path::{Path, PathBuf};
12use tracing::{debug, error, trace, warn};
13
14type FuncDesc = (u64, u64, FunctionType, Option<String>);
16
17#[derive(Debug, Clone, Copy, PartialEq)]
18enum FunctionType {
19 Generated,
20 Test,
21 Standard,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
25pub enum LineType {
26 TestMain,
28 TestEntry(u64),
30 FunctionEntry(u64),
32 Statement,
34 Condition,
36 Unknown,
38 UnusedGeneric,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Hash)]
43struct SourceLocation {
44 pub path: PathBuf,
45 pub line: u64,
46}
47
48impl From<(PathBuf, usize)> for SourceLocation {
49 fn from(other: (PathBuf, usize)) -> Self {
50 Self {
51 path: other.0,
52 line: other.1 as u64,
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
58pub struct TracerData {
59 pub trace_type: LineType,
62 pub address: Option<u64>,
64 pub length: u64,
66 pub fn_name: Option<String>,
68}
69
70fn generate_func_desc<R, Offset>(
71 die: &DebuggingInformationEntry<R, Offset>,
72 debug_str: &DebugStr<R>,
73) -> Result<FuncDesc>
74where
75 R: Reader<Offset = Offset>,
76 Offset: ReaderOffset,
77{
78 let mut func_type = FunctionType::Standard;
79 let low = die.attr_value(DW_AT_low_pc)?;
80 let high = die.attr_value(DW_AT_high_pc)?;
81 let linkage = die.attr_value(DW_AT_linkage_name)?;
82 let fn_name = die.attr_value(DW_AT_name)?;
83
84 let fn_name: Option<String> = match fn_name {
85 Some(AttributeValue::DebugStrRef(offset)) => debug_str
86 .get_str(offset)
87 .and_then(|r| r.to_string().map(|s| s.to_string()))
88 .ok()
89 .map(|r| demangle(r.as_ref()).to_string()),
90 _ => None,
91 };
92
93 let low = match low {
95 Some(AttributeValue::Addr(x)) => x,
96 _ => 0u64,
97 };
98 let high = match high {
100 Some(AttributeValue::Udata(x)) => x,
101 _ => 0u64,
102 };
103 if let Some(AttributeValue::DebugStrRef(offset)) = linkage {
104 let name = debug_str
105 .get_str(offset)
106 .and_then(|r| r.to_string().map(|s| s.to_string()))
107 .unwrap_or_else(|_| "".into());
108 let name = demangle(name.as_ref()).to_string();
109 func_type = if name.contains("tests::") {
112 FunctionType::Test
113 } else if name.contains("__test::main") {
114 FunctionType::Generated
115 } else {
116 FunctionType::Standard
117 };
118 }
119 Ok((low, high, func_type, fn_name))
120}
121
122fn get_entry_points<R, Offset>(
125 debug_info: &UnitHeader<R, Offset>,
126 debug_abbrev: &Abbreviations,
127 debug_str: &DebugStr<R>,
128) -> Vec<FuncDesc>
129where
130 R: Reader<Offset = Offset>,
131 Offset: ReaderOffset,
132{
133 let mut result: Vec<FuncDesc> = Vec::new();
134 let mut cursor = debug_info.entries(debug_abbrev);
135 let _ = cursor.next_entry();
137 while let Ok(Some((_, node))) = cursor.next_dfs() {
138 if node.tag() == DW_TAG_subprogram {
140 if let Ok(fd) = generate_func_desc(node, debug_str) {
141 result.push(fd);
142 }
143 }
144 }
145 result
146}
147
148fn get_addresses_from_program<R, Offset>(
149 prog: IncompleteLineProgram<R>,
150 debug_strs: &DebugStr<R>,
151 entries: &[(u64, LineType, &Option<String>)],
152 config: &Config,
153 result: &mut HashMap<SourceLocation, Vec<TracerData>>,
154) -> Result<()>
155where
156 R: Reader<Offset = Offset>,
157 Offset: ReaderOffset,
158{
159 let project = config.root();
160 let get_string = |x: R| x.to_string().map(|y| y.to_string()).ok();
161 let (cprog, seq) = prog.sequences()?;
162 for s in seq {
163 let mut sm = cprog.resume_from(&s);
164 while let Ok(Some((header, &ln_row))) = sm.next_row() {
165 if ln_row.end_sequence() {
166 break;
167 }
168 if !ln_row.is_stmt() || ln_row.line().is_none() {
170 continue;
171 }
172 if let Some(file) = ln_row.file(header) {
173 let mut path = project.clone();
174 if let Some(dir) = file.directory(header) {
175 if let Some(temp) = dir.string_value(debug_strs).and_then(get_string) {
176 path.push(temp);
177 }
178 }
179 if let Ok(p) = path.canonicalize() {
180 path = fix_unc_path(&p);
181 }
182 let file = file.path_name();
183 let line = ln_row.line().unwrap();
184 if let Some(file) = file.string_value(debug_strs).and_then(get_string) {
185 path.push(file);
186 if !path.is_file() {
187 continue;
189 }
190 if is_coverable_file_path(&path, &project, &config.target_dir()) {
191 let address = ln_row.address();
192 let (desc, fn_name) = entries
193 .iter()
194 .filter(|&&(addr, _, _)| addr == address)
195 .map(|&(_, t, fn_name)| (t, fn_name.clone()))
196 .next()
197 .unwrap_or((LineType::Unknown, None));
198 let loc = SourceLocation {
199 path,
200 line: line.into(),
201 };
202 if desc != LineType::TestMain {
203 let trace = TracerData {
204 address: Some(address),
205 trace_type: desc,
206 length: 1,
207 fn_name,
208 };
209 let tracerdata = result.entry(loc).or_default();
210 tracerdata.push(trace);
211 }
212 }
213 }
214 }
215 }
216 }
217 Ok(())
218}
219
220fn get_line_addresses<'data>(
221 endian: RunTimeEndian,
222 obj: &'data impl object::read::Object<'data>,
223 analysis: &HashMap<PathBuf, LineAnalysis>,
224 config: &Config,
225) -> Result<TraceMap> {
226 let project = config.root();
227 let io_err = |e| {
228 error!("IO error parsing section: {e}");
229 Error::Io
230 };
231 trace!("Reading object sections");
232 let mut result = TraceMap::new();
233 trace!("Reading .debug_info");
234 let debug_info = obj.section_by_name(".debug_info").ok_or(Error::Io)?;
235 let debug_info = DebugInfo::new(debug_info.data().map_err(io_err)?, endian);
236 trace!("Reading .debug_abbrev");
237 let debug_abbrev = obj.section_by_name(".debug_abbrev").ok_or(Error::Io)?;
238 let debug_abbrev = DebugAbbrev::new(debug_abbrev.data().map_err(io_err)?, endian);
239 trace!("Reading .debug_str");
240 let debug_strings = obj.section_by_name(".debug_str").ok_or(Error::Io)?;
241 let debug_strings = DebugStr::new(debug_strings.data().map_err(io_err)?, endian);
242 trace!("Reading .debug_line");
243 let debug_line = obj.section_by_name(".debug_line").ok_or(Error::Io)?;
244 let debug_line = DebugLine::new(debug_line.data().map_err(io_err)?, endian);
245
246 trace!("Reading .text");
247 let base_addr = obj.section_by_name(".text").ok_or(Error::Io)?;
248
249 trace!("Reading DebugInfo units");
250 let mut iter = debug_info.units();
251 while let Ok(Some(cu)) = iter.next() {
252 let addr_size = cu.address_size();
253 let abbr = match cu.abbreviations(&debug_abbrev) {
254 Ok(a) => a,
255 _ => continue,
256 };
257 let entry_points = get_entry_points(&cu, &abbr, &debug_strings);
258 let entries = entry_points
259 .iter()
260 .map(|(a, b, c, fn_name)| match c {
261 FunctionType::Test => (*a, LineType::TestEntry(*b), fn_name),
262 FunctionType::Standard => (*a, LineType::FunctionEntry(*b), fn_name),
263 FunctionType::Generated => (*a, LineType::TestMain, fn_name),
264 })
265 .collect::<Vec<_>>();
266
267 if let Ok(Some((_, root))) = cu.entries(&abbr).next_dfs() {
268 let offset = match root.attr_value(DW_AT_stmt_list) {
269 Ok(Some(AttributeValue::DebugLineRef(o))) => o,
270 _ => continue,
271 };
272 let prog = debug_line.program(offset, addr_size, None, None)?; let mut temp_map: HashMap<SourceLocation, Vec<TracerData>> = HashMap::new();
274
275 if let Err(e) =
276 get_addresses_from_program(prog, &debug_strings, &entries, config, &mut temp_map)
277 {
278 debug!("Potential issue reading test addresses {}", e);
279 } else {
280 for v in temp_map.values_mut() {
282 v.dedup_by_key(|x| x.address);
283 }
284 let temp_map = temp_map
285 .into_iter()
286 .filter(|(ref k, _)| {
287 config.include_tests() || !k.path.starts_with(project.join("tests"))
288 })
289 .filter(|(ref k, _)| !(config.exclude_path(&k.path)))
290 .filter(|(ref k, _)| config.include_path(&k.path))
291 .filter(|(ref k, _)| {
292 !analysis.should_ignore(k.path.as_ref(), &(k.line as usize))
293 })
294 .map(|(k, v)| {
295 let ret = analysis.normalise(k.path.as_ref(), k.line as usize);
296 let k_n = SourceLocation::from(ret);
297 (k_n, v)
298 })
299 .collect::<HashMap<SourceLocation, Vec<TracerData>>>();
300
301 let mut tracemap = TraceMap::new();
302 for (k, val) in temp_map.iter().filter(|(k, _)| k.line != 0) {
303 let rpath = config.strip_base_dir(&k.path);
304 let mut address = HashSet::new();
305 let mut fn_name = None;
306 for v in val.iter() {
307 if let Some(a) = v.address {
308 if a < base_addr.address()
309 && a >= (base_addr.address() + base_addr.size())
310 {
311 continue;
312 }
313 address.insert(a);
314 trace!(
315 "Adding trace at address 0x{:x} in {}:{}",
316 a,
317 rpath.display(),
318 k.line
319 );
320 }
321 if fn_name.is_none() && v.fn_name.is_some() {
322 fn_name = v.fn_name.clone();
323 }
324 }
325 if address.is_empty() {
326 trace!(
327 "Adding trace with no address at {}:{}",
328 rpath.display(),
329 k.line
330 );
331 }
332 tracemap.add_trace(&k.path, Trace::new(k.line, address, 1));
333 }
334 result.merge(&tracemap);
335 }
336 }
337 }
338
339 add_line_analysis(analysis, config, &mut result);
340 Ok(result)
341}
342
343fn add_line_analysis(
344 in_analysis: &HashMap<PathBuf, LineAnalysis>,
345 in_config: &Config,
346 in_out_trace: &mut TraceMap,
347) {
348 for (file, line_analysis) in in_analysis.iter() {
349 if in_config.exclude_path(file) || !in_config.include_path(file) {
350 continue;
351 }
352 for line in &line_analysis.cover {
353 let line = *line as u64;
354 if !in_out_trace.contains_location(file, line)
355 && !line_analysis.should_ignore(line as usize)
356 {
357 let rpath = in_config.strip_base_dir(file);
358 trace!(
359 "Adding trace for potentially uncoverable line in {}:{}",
360 rpath.display(),
361 line
362 );
363 in_out_trace.add_trace(file, Trace::new_stub(line));
364 }
365 }
366 }
367}
368
369#[cfg(ptrace_supported)]
370fn open_symbols_file(test: &Path) -> io::Result<File> {
371 File::open(test)
372}
373
374#[cfg(not(ptrace_supported))]
383fn open_symbols_file(_test: &Path) -> io::Result<File> {
384 Err(io::Error::new(
385 io::ErrorKind::Other,
386 "Symbol files aren't read on non-ptrace systems",
387 ))
388}
389
390pub fn generate_tracemap(
391 test: &Path,
392 analysis: &HashMap<PathBuf, LineAnalysis>,
393 config: &Config,
394) -> io::Result<TraceMap> {
395 trace!("Generating traces for {}", test.display());
396 let file = match open_symbols_file(test) {
397 Ok(s) => object::read::ReadCache::new(s),
398 Err(e) if config.engine() != TraceEngine::Llvm => return Err(e),
399 _ => {
400 return Ok(TraceMap::new());
401 }
402 };
403 let obj = object::File::parse(&file)
404 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Unable to parse binary"))?;
405 let endian = if obj.is_little_endian() {
406 RunTimeEndian::Little
407 } else {
408 RunTimeEndian::Big
409 };
410 get_line_addresses(endian, &obj, analysis, config)
411 .map_err(|e| {
412 error!("Error parsing debug information from binary: {}", e);
414 warn!("Stripping symbol information can prevent tarpaulin from working. If you want to do this pass `--engine=llvm`");
415 io::Error::new(io::ErrorKind::InvalidData, "Error while parsing binary or DWARF info.")
416 })
417}