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}