1pub(crate) mod cu;
4pub(crate) mod navigation;
5pub(crate) mod unit;
6pub(crate) mod utils;
7
8use std::fmt;
9
10use anyhow::Context;
11pub(crate) use cu::CompilationUnitId;
12use gimli::UnitSectionOffset;
13pub(crate) use unit::UnitRef;
14pub(crate) use utils::{file_entry_to_path, get_unit_ref_attr, parse_die_string_attribute};
15
16use crate::{
17 die::utils::pretty_print_die_entry,
18 file::{
19 loader::{DwarfReader, Offset, RawDie},
20 DebugFile, SourceFile, SourceLocation,
21 },
22 DwarfDb,
23};
24
25#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, salsa::Update)]
27pub struct Die {
28 pub(crate) file: DebugFile,
29 pub(crate) cu_offset: UnitSectionOffset<usize>,
30 pub(crate) die_offset: Offset,
31}
32
33impl fmt::Debug for Die {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 salsa::with_attached_database(|db| write!(f, "Die {}", self.location(db)))
36 .unwrap_or_else(|| write!(f, "Die at {:?} {:#010x}", self.file, self.offset()))
37 }
38}
39
40impl Die {
41 pub(crate) fn new(
42 file: DebugFile,
43 cu_offset: UnitSectionOffset<usize>,
44 die_offset: Offset,
45 ) -> Self {
46 Self {
47 file,
48 cu_offset,
49 die_offset,
50 }
51 }
52}
53
54struct DieLocation {
55 path: String,
56 die_offset: usize,
57}
58
59pub struct DieAccessError {
60 inner: anyhow::Error,
61 location: DieLocation,
62}
63
64trait ResExt {
65 type V;
66 fn into_die_result(self, db: &dyn DwarfDb, die: &Die) -> Result<Self::V>;
67}
68
69impl<V> ResExt for anyhow::Result<V> {
70 type V = V;
71 fn into_die_result(self, db: &dyn DwarfDb, die: &Die) -> Result<V> {
72 self.map_err(|e| die.make_error(db, e))
73 }
74}
75
76type Result<T> = std::result::Result<T, DieAccessError>;
77
78impl fmt::Debug for DieAccessError {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 write!(f, "{self}")
81 }
82}
83
84impl fmt::Display for DieAccessError {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 let Self {
87 inner,
88 location: DieLocation { path, die_offset },
89 } = self;
90 write!(
91 f,
92 "Die access error at {path} {die_offset:#010x}: {inner:?}",
93 )
94 }
95}
96
97impl std::error::Error for DieAccessError {
98 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
99 self.inner.source()
100 }
101
102 fn cause(&self) -> Option<&dyn std::error::Error> {
103 Some(self.inner.as_ref())
104 }
105}
106
107impl Die {
108 pub(crate) fn cu(&self) -> CompilationUnitId {
111 CompilationUnitId::new(self.file, self.cu_offset)
112 }
113
114 pub(crate) fn children(&self, db: &dyn DwarfDb) -> Result<Vec<Die>> {
116 let mut children = vec![];
117
118 let unit_ref = self.unit_ref(db)?;
119
120 let mut tree = unit_ref
121 .entries_tree(Some(self.die_offset))
122 .context("Failed to get children nodes")
123 .into_die_result(db, self)?;
124 let tree_root = tree
125 .root()
126 .context("Failed to get children nodes")
127 .into_die_result(db, self)?;
128
129 let mut child_nodes = tree_root.children();
130
131 while let Some(child) = child_nodes
132 .next()
133 .context("Failed to parse child nodes")
134 .into_die_result(db, self)?
135 {
136 let child_offset = child.entry().offset();
137 let child_die = Die::new(self.file, self.cu_offset, child_offset);
138 children.push(child_die);
139 }
140
141 Ok(children)
142 }
143
144 pub(crate) fn make_error<E: Into<anyhow::Error>>(
145 &self,
146 db: &dyn DwarfDb,
147 error: E,
148 ) -> DieAccessError {
149 DieAccessError {
150 inner: error.into(),
151 location: DieLocation {
152 path: self.file.name(db),
153 die_offset: self.die_offset.0,
154 },
155 }
156 }
157
158 pub(crate) fn offset(&self) -> usize {
160 self.cu_offset.as_debug_info_offset().unwrap().0 + self.die_offset.0
161 }
162
163 pub fn location(&self, db: &dyn salsa::Database) -> String {
164 format!("{} {:#010x}", self.file.name(db), self.offset())
165 }
166
167 pub(crate) fn format_with_location<T: AsRef<str>>(
168 &self,
169 db: &dyn DwarfDb,
170 message: T,
171 ) -> String {
172 format!(
173 "{} for {} {:#010x}",
174 message.as_ref(),
175 self.file.name(db),
176 self.offset(),
177 )
178 }
179
180 pub(crate) fn get_referenced_entry(&self, db: &dyn DwarfDb, attr: gimli::DwAt) -> Result<Die> {
181 self.with_entry_and_unit(db, |entry, _| {
182 get_unit_ref_attr(entry, attr)
183 .map(|unit_offset| Die::new(self.file, self.cu_offset, unit_offset))
184 .into_die_result(db, self)
185 })?
186 }
187
188 pub(crate) fn tag(&self, db: &dyn DwarfDb) -> gimli::DwTag {
189 self.with_entry(db, |entry| entry.tag())
190 .unwrap_or(gimli::DW_TAG_null)
191 }
192
193 pub fn name(&self, db: &dyn DwarfDb) -> Result<String> {
194 self.string_attr(db, gimli::DW_AT_name)
195 }
196
197 pub(crate) fn get_member(&self, db: &dyn DwarfDb, name: &str) -> Result<Die> {
200 self.children(db)?
201 .into_iter()
202 .find(|child| child.name(db).is_ok_and(|n| n == name))
203 .with_context(|| format!("Failed to find member `{name}`"))
204 .into_die_result(db, self)
205 }
206
207 pub(crate) fn get_member_by_tag(&self, db: &dyn DwarfDb, tag: gimli::DwTag) -> Result<Die> {
208 self.children(db)?
209 .into_iter()
210 .find(|child| child.tag(db) == tag)
211 .with_context(|| format!("Failed to find member with tag `{tag:?}`"))
212 .into_die_result(db, self)
213 }
214
215 pub(crate) fn get_udata_member_attribute(
216 &self,
217 db: &dyn DwarfDb,
218 name: &str,
219 attr: gimli::DwAt,
220 ) -> Result<usize> {
221 self.get_member(db, name)?.udata_attr(db, attr)
222 }
223
224 pub(crate) fn get_generic_type_entry(&self, db: &dyn DwarfDb, name: &str) -> Result<Die> {
225 self.children(db)?
226 .into_iter()
227 .find(|child| {
228 child.tag(db) == gimli::DW_TAG_template_type_parameter
229 && child.name(db).is_ok_and(|n| n == name)
230 })
231 .with_context(|| format!("Failed to find generic type entry `{name}`"))
232 .into_die_result(db, self)
233 .and_then(|member| member.get_referenced_entry(db, gimli::DW_AT_type))
234 }
235
236 pub(crate) fn get_attr(
237 &self,
238 db: &dyn DwarfDb,
239 attr: gimli::DwAt,
240 ) -> Result<gimli::AttributeValue<DwarfReader>> {
241 Ok(self
242 .with_entry(db, |entry| entry.attr(attr))?
243 .with_context(|| format!("error fetching attribute {attr}"))
244 .into_die_result(db, self)?
245 .with_context(|| format!("attribute {attr} not found"))
246 .into_die_result(db, self)?
247 .value())
248 }
249
250 pub(crate) fn string_attr(&self, db: &dyn DwarfDb, attr: gimli::DwAt) -> Result<String> {
251 self.with_entry_and_unit(db, |entry, unit_ref| {
252 parse_die_string_attribute(entry, attr, unit_ref).into_die_result(db, self)
253 })?
254 }
255
256 pub(crate) fn udata_attr(&self, db: &dyn DwarfDb, attr: gimli::DwAt) -> Result<usize> {
257 let v = self.get_attr(db, attr)?;
258
259 v.udata_value()
260 .with_context(|| format!("attr {attr} is not a udata value, got: {v:?}"))
261 .map(|v| v as usize)
262 .into_die_result(db, self)
263 }
264
265 pub(crate) fn print(&self, db: &dyn DwarfDb) -> String {
266 self.with_entry_and_unit(db, |entry, unit_ref| {
267 self.format_with_location(db, pretty_print_die_entry(entry, unit_ref))
268 })
269 .unwrap_or_else(|e| {
270 tracing::error!("Failed to print DIE entry: {e}");
271 "entry not found".to_string()
272 })
273 }
274
275 pub(super) fn with_entry_and_unit<F: FnOnce(&RawDie<'_>, &UnitRef<'_>) -> T, T>(
278 &self,
279 db: &dyn DwarfDb,
280 f: F,
281 ) -> Result<T> {
282 let unit_ref = self.unit_ref(db)?;
283 let entry = self.entry(db, &unit_ref)?;
284 Ok(f(&entry, &unit_ref))
285 }
286
287 pub(crate) fn unit_ref<'db>(&self, db: &'db dyn DwarfDb) -> Result<UnitRef<'db>> {
288 self.cu()
289 .unit_ref(db)
290 .context("Failed to get unit reference")
291 .into_die_result(db, self)
292 }
293
294 fn with_entry<F: FnOnce(&RawDie<'_>) -> T, T>(&self, db: &dyn DwarfDb, f: F) -> Result<T> {
295 let unit_ref = self.unit_ref(db)?;
296 let entry = self.entry(db, &unit_ref)?;
297 Ok(f(&entry))
298 }
299
300 fn entry<'a>(&self, db: &dyn DwarfDb, unit_ref: &'a UnitRef<'_>) -> Result<RawDie<'a>> {
301 unit_ref
302 .entry(self.die_offset)
303 .context("Failed to get DIE entry")
304 .into_die_result(db, self)
305 }
306}
307
308pub(crate) fn position(db: &dyn DwarfDb, entry: Die) -> Result<Option<SourceLocation>> {
310 let Ok(decl_file_attr) = entry.get_attr(db, gimli::DW_AT_decl_file) else {
311 return Ok(None);
312 };
313 let Ok(decl_line) = entry.udata_attr(db, gimli::DW_AT_decl_line) else {
314 return Ok(None);
315 };
316 let gimli::AttributeValue::FileIndex(file_idx) = decl_file_attr else {
317 return Err(entry.make_error(
318 db,
319 anyhow::anyhow!("Expected DW_AT_decl_file to be a FileIndex, got: {decl_file_attr:?}"),
320 ));
321 };
322
323 let unit_ref = entry.unit_ref(db)?;
324
325 let Some(line_program) = unit_ref.line_program.clone() else {
327 return Err(entry.make_error(db, anyhow::anyhow!("Failed to parse line program")));
328 };
329 let header = line_program.header();
330 let Some(file) = header.file(file_idx) else {
331 return Err(entry.make_error(
332 db,
333 anyhow::anyhow!("Failed to parse file index: {:#?}", header.file_names()),
334 ));
335 };
336
337 let Some(path) = file_entry_to_path(db, file, &unit_ref) else {
338 return Err(entry.make_error(db, anyhow::anyhow!("Failed to convert file entry to path")));
339 };
340
341 let source_file = SourceFile::new(path);
342 Ok(Some(SourceLocation::new(
343 source_file,
344 decl_line as u64,
345 None,
346 )))
347}