rust_debug/source_information.rs
1use anyhow::{anyhow, Result};
2use log::error;
3
4use crate::utils::get_current_unit;
5
6use gimli::{ColumnType, DebuggingInformationEntry, Dwarf, Reader, Unit};
7use std::num::NonZeroU64;
8
9/// Contains all the information about where the code was declared in the source code.
10#[derive(Debug, Clone)]
11pub struct SourceInformation {
12 /// The source code directory where the debug information was declared.
13 pub directory: Option<String>,
14
15 /// The relative source code file path where the debug information was declared.
16 pub file: Option<String>,
17
18 /// The source code line number where the debug information was declared.
19 pub line: Option<NonZeroU64>,
20
21 /// The source code column number where the debug information was declared.
22 pub column: Option<NonZeroU64>,
23}
24
25impl SourceInformation {
26 /// Retrieves the information about where the given DIE was declared in the source code.
27 ///
28 /// Description:
29 ///
30 /// * `dwarf` - A reference to gimli-rs `Dwarf` struct.
31 /// * `unit` - A reference to gimli-rs `Unit` struct, which the given DIE is located in.
32 /// * `die` - A reference to the DIE containing attributes starting with `DW_AT_decl_`.
33 /// * `cwd` - The work directory of the debugged program.
34 ///
35 ///This function will retrieve the information stored in the attributes starting with
36 ///`DW_AT_decl_` from the given DIE>
37 pub fn get_die_source_information<R: Reader<Offset = usize>>(
38 dwarf: &Dwarf<R>,
39 unit: &Unit<R>,
40 die: &DebuggingInformationEntry<'_, '_, R>,
41 cwd: &str,
42 ) -> Result<SourceInformation> {
43 let (file, directory) = match die.attr_value(gimli::DW_AT_decl_file)? {
44 Some(gimli::AttributeValue::FileIndex(v)) => match &unit.line_program {
45 Some(lp) => {
46 let header = lp.header();
47 match header.file(v) {
48 Some(file_entry) => {
49 let (file, directory) = match file_entry.directory(header) {
50 Some(dir_av) => {
51 let mut dir_raw =
52 dwarf.attr_string(unit, dir_av)?.to_string()?.to_string();
53 let file_raw = dwarf
54 .attr_string(unit, file_entry.path_name())?
55 .to_string()?
56 .to_string();
57 let file = file_raw.trim_start_matches(&dir_raw).to_string();
58
59 if !dir_raw.starts_with('/') {
60 dir_raw = format!("{}/{}", cwd, dir_raw);
61 }
62
63 (file, Some(dir_raw))
64 }
65 None => (
66 dwarf
67 .attr_string(unit, file_entry.path_name())?
68 .to_string()?
69 .to_string(),
70 None,
71 ),
72 };
73
74 (Some(file), directory)
75 }
76 None => (None, None),
77 }
78 }
79 None => (None, None),
80 },
81 None => (None, None),
82 Some(v) => {
83 error!("Unimplemented {:?}", v);
84 return Err(anyhow!("Unimplemented {:?}", v));
85 }
86 };
87
88 let line = match die.attr_value(gimli::DW_AT_decl_line)? {
89 Some(gimli::AttributeValue::Udata(v)) => NonZeroU64::new(v),
90 None => None,
91 Some(v) => {
92 error!("Unimplemented {:?}", v);
93 return Err(anyhow!("Unimplemented {:?}", v));
94 }
95 };
96
97 let column = match die.attr_value(gimli::DW_AT_decl_column)? {
98 Some(gimli::AttributeValue::Udata(v)) => NonZeroU64::new(v),
99 None => None,
100 Some(v) => {
101 error!("Unimplemented {:?}", v);
102 return Err(anyhow!("Unimplemented {:?}", v));
103 }
104 };
105
106 Ok(SourceInformation {
107 directory,
108 file,
109 line,
110 column,
111 })
112 }
113
114 pub fn get_from_address<R: Reader<Offset = usize>>(
115 dwarf: &Dwarf<R>,
116 address: u64,
117 cwd: &str,
118 ) -> Result<SourceInformation> {
119 let unit = get_current_unit(dwarf, address as u32)?;
120 let mut nearest = None;
121 match unit.line_program.clone() {
122 Some(line_program) => {
123 let (program, sequences) = line_program.sequences()?;
124 let mut in_range_seqs = vec![];
125 for seq in sequences {
126 if address >= seq.start && address < seq.end {
127 in_range_seqs.push(seq);
128 }
129 }
130 // println!("number of seqs: {:?}", in_range_seqs.len());
131 // println!("pc: {:?}", address);
132 let mut result = vec![];
133 //let mut all = 0;
134 for seq in in_range_seqs {
135 let mut sm = program.resume_from(&seq);
136 while let Some((header, row)) = sm.next_row()? {
137 // println!(
138 // "address: {:?}, line: {:?}, is_stmt: {:?}, valid: {:?}",
139 // row.address(),
140 // row.line(),
141 // row.is_stmt(),
142 // row.address() == address
143 // );
144
145 if row.address() <= address {
146 let (file, directory) = match row.file(header) {
147 Some(file_entry) => match file_entry.directory(header) {
148 Some(dir_av) => {
149 let mut dir_raw = dwarf
150 .attr_string(&unit, dir_av)?
151 .to_string()?
152 .to_string();
153 let file_raw = dwarf
154 .attr_string(&unit, file_entry.path_name())?
155 .to_string()?
156 .to_string();
157 let file =
158 file_raw.trim_start_matches(&dir_raw).to_string();
159
160 if !dir_raw.starts_with('/') {
161 dir_raw = format!("{}/{}", cwd, dir_raw);
162 }
163
164 (Some(file), Some(dir_raw))
165 }
166 None => (None, None),
167 },
168 None => (None, None),
169 };
170
171 let si = SourceInformation {
172 directory,
173 file,
174 line: row.line(),
175 column: match row.column() {
176 ColumnType::LeftEdge => NonZeroU64::new(1),
177 ColumnType::Column(n) => Some(n),
178 },
179 };
180
181 match nearest {
182 Some((addr, _)) => {
183 if row.address() > addr {
184 nearest = Some((row.address(), si));
185 }
186 }
187 None => nearest = Some((row.address(), si)),
188 };
189 }
190 if row.address() == address {
191 result.push(row.line());
192 }
193 // all += 1;
194 }
195 }
196 //println!("total line rows: {:?}", all);
197 // println!("result line rows: {:?}", result.len());
198 match nearest {
199 Some((_, si)) => Ok(si),
200 None => {
201 error!("Could not find source informaitno");
202 Err(anyhow!("Could not find source informaitno"))
203 }
204 }
205 }
206 None => {
207 error!("Unit has no line program");
208 Err(anyhow!("Unit has no line program"))
209 }
210 }
211 }
212}
213
214/// Find the machine code address that corresponds to a line in the source file.
215///
216/// Description:
217///
218/// * `dwarf` - A reference to gimli-rs `Dwarf` struct.
219/// * `cwd` - The work directory of the debugged program.
220/// * `path` - The relative path to the source file from the work directory of the debugged program.
221/// * `line` - A line number in the source program.
222/// * `column` - A optional column number in the source program.
223///
224/// Finds the machine code address that is generated from the given source code file and line
225/// number.
226/// If there are multiple machine codes for that line number it takes the first one and the one.
227// Good source: DWARF section 6.2
228pub fn find_breakpoint_location<'a, R: Reader<Offset = usize>>(
229 dwarf: &'a Dwarf<R>,
230 cwd: &str,
231 path: &str,
232 line: NonZeroU64,
233 column: Option<NonZeroU64>,
234) -> Result<Option<u64>> {
235 let mut locations = vec![];
236
237 let mut units = dwarf.units();
238 while let Some(unit_header) = units.next()? {
239 let unit = dwarf.unit(unit_header)?;
240
241 if let Some(ref line_program) = unit.line_program {
242 let lp_header = line_program.header();
243
244 for file_entry in lp_header.file_names() {
245 let directory = match file_entry.directory(lp_header) {
246 Some(dir_av) => {
247 let dir_raw = dwarf.attr_string(&unit, dir_av)?;
248 dir_raw.to_string()?.to_string()
249 }
250 None => continue,
251 };
252
253 let file_raw = dwarf.attr_string(&unit, file_entry.path_name())?;
254 let mut file_path = format!("{}/{}", directory, file_raw.to_string()?);
255
256 if !file_path.starts_with('/') {
257 // TODO: Find a better solution
258 file_path = format!("{}/{}", cwd, file_path);
259 }
260
261 if path == file_path {
262 let mut rows = line_program.clone().rows();
263 while let Some((header, row)) = rows.next_row()? {
264 let file_entry = match row.file(header) {
265 Some(v) => v,
266 None => continue,
267 };
268
269 let directory = match file_entry.directory(header) {
270 Some(dir_av) => {
271 let dir_raw = dwarf.attr_string(&unit, dir_av)?;
272 dir_raw.to_string()?.to_string()
273 }
274 None => continue,
275 };
276
277 let file_raw = dwarf.attr_string(&unit, file_entry.path_name())?;
278 let mut file_path = format!("{}/{}", directory, file_raw.to_string()?);
279 if !file_path.starts_with('/') {
280 // TODO: Find a better solution
281 file_path = format!("{}/{}", cwd, file_path);
282 }
283
284 if path == file_path {
285 if let Some(l) = row.line() {
286 if line == l {
287 locations.push((row.column(), row.address()));
288 }
289 }
290 }
291 }
292 }
293 }
294 }
295 }
296
297 match locations.len() {
298 0 => Ok(None),
299 len => {
300 let search = match column {
301 Some(v) => gimli::ColumnType::Column(v),
302 None => gimli::ColumnType::LeftEdge,
303 };
304
305 let mut res = locations[0];
306 for location in locations.iter().take(len).skip(1) {
307 if location.0 <= search && location.0 > res.0 {
308 res = *location;
309 }
310 }
311
312 Ok(Some(res.1))
313 }
314 }
315}