1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
use crate::debugger::address::RelocatedAddress;
use crate::debugger::debugee::dwarf::{DebugInformation, EndianArcSlice};
use crate::debugger::error::Error;
use crate::debugger::error::Error::MappingNotFound;
use nix::unistd::Pid;
use proc_maps::MapRange;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
/// Memory region range.
#[derive(Debug, Clone)]
pub struct RegionRange {
pub from: RelocatedAddress,
pub to: RelocatedAddress,
}
/// Information about loaded in VAS region.
pub struct RegionInfo {
pub path: PathBuf,
pub has_debug_info: bool,
pub range: Option<RegionRange>,
}
/// Difference between two registry states (typically between current and needle)
pub struct ReloadPlan {
pub to_del: Vec<PathBuf>,
pub to_add: Vec<PathBuf>,
}
/// Registry contains debug information about main executable object and loaded shared libraries.
pub struct DwarfRegistry {
/// process pid
pid: Pid,
/// main executable file
program_path: PathBuf,
/// debug information map
files: HashMap<PathBuf, DebugInformation>,
/// ordered .text section address ranges, calculates by dwarf units ranges
ranges: Vec<(PathBuf, RegionRange)>,
/// regions map addresses, each region is a shared lib or debugee program
mappings: HashMap<PathBuf, usize>,
}
impl DwarfRegistry {
/// Create new registry.
///
/// # Arguments
///
/// * `pid`: program process pid
/// * `program_path`: path to program executable
/// * `program_dwarf`: program dwarf information
pub fn new(
pid: Pid,
program_path: PathBuf,
program_dwarf: DebugInformation<EndianArcSlice>,
) -> Self {
Self {
pid,
program_path: program_path.clone(),
files: HashMap::from([(program_path, program_dwarf)]),
ranges: vec![],
mappings: HashMap::new(),
}
}
/// Update ranges with respect of VAS segments addresses.
/// Must be called after program is loaded into memory.
///
/// # Arguments
///
/// * `only_main`: if true - update mappings only for main executable file, false - update all
pub fn update_mappings(&mut self, only_main: bool) -> Result<Vec<Error>, Error> {
let proc_maps: Vec<MapRange> = proc_maps::get_process_maps(self.pid.as_raw())?;
let mut mappings = HashMap::with_capacity(self.files.len());
let mut ranges = vec![];
let mut errors = Vec::new();
let (mut full_it, mut only_main_it);
let iter: &mut dyn Iterator<Item = (&PathBuf, &DebugInformation)> = if only_main {
only_main_it = self
.files
.iter()
.filter(|(file, _)| *file == &self.program_path);
&mut only_main_it
} else {
full_it = self.files.iter();
&mut full_it
};
iter.for_each(|(file, _)| {
let absolute_debugee_path_buf =
file.canonicalize().expect("canonicalize path must exists");
let absolute_debugee_path = absolute_debugee_path_buf.as_path();
let maps = proc_maps
.iter()
.filter(|map| map.filename() == Some(absolute_debugee_path))
.collect::<Vec<_>>();
if maps.is_empty() {
errors.push(MappingNotFound(file.to_string_lossy().to_string()));
return;
}
let lower_sect = maps
.iter()
.min_by(|map1, map2| map1.start().cmp(&map2.start()))
.expect("at least one mapping must exists");
let higher_sect = maps
.iter()
.max_by(|map1, map2| map1.start().cmp(&map2.start()))
.expect("at least one mapping must exists");
let mapping = lower_sect.start();
let range = RegionRange {
from: RelocatedAddress::from(lower_sect.start()),
to: RelocatedAddress::from(higher_sect.start() + higher_sect.size()),
};
mappings.insert(file.clone(), mapping);
ranges.push((file.clone(), range));
});
self.mappings = mappings;
ranges.sort_unstable_by(|(_, r1), (_, r2)| r1.from.cmp(&r2.from));
self.ranges = ranges;
Ok(errors)
}
/// Add new debug information into registry.
///
/// # Arguments
///
/// * `file`: path to executable object or shared lib
/// * `dwarf`: parsed dwarf information
pub fn add(
&mut self,
file: impl Into<PathBuf>,
dwarf: DebugInformation<EndianArcSlice>,
) -> Result<(), Error> {
let path = file.into();
// validate path
path.canonicalize()?;
self.files.insert(path, dwarf);
Ok(())
}
pub fn remove(&mut self, path: impl AsRef<Path>) {
let path = path.as_ref();
self.files.remove(path);
self.mappings.remove(path);
self.ranges.retain_mut(|(lib_path, _)| lib_path != path);
}
/// Calculate a difference between two list of paths - already parsed libs list (from this registry)
/// and target list of libraries in argument. This difference using for create a
/// [`ReloadPlan`] - an indication to the caller which library can be deleted from registry and which
/// need to be parsed and add to the registry. After this plan is executed registry will contains
/// libraries from `target` list.
///
/// # Arguments
///
/// * `target`: list of shared libraries paths
///
/// returns: [`ReloadPlan`] - must executed at caller side
pub fn reload_plan(&self, target: Vec<PathBuf>) -> ReloadPlan {
let current_libs: Vec<_> = self.files.keys().cloned().collect();
let mut to_del = vec![];
for current_lib in current_libs {
// program executable cannot be deleted or reloaded
if !target.contains(¤t_lib) && current_lib != self.program_path {
to_del.push(current_lib);
}
}
let mut to_add = vec![];
for target_lib in target {
if !self.files.contains_key(&target_lib) {
to_add.push(target_lib);
}
}
ReloadPlan { to_del, to_add }
}
/// Return all known debug information. Debug info about main executable object
/// is located at the zero index, other information ordered from less compilation
/// unit count to greatest.
pub fn all_dwarf(&self) -> Vec<&DebugInformation> {
let mut dwarfs: Vec<_> = self.files.values().collect();
dwarfs.sort_unstable_by(|d1, d2| {
if d1.pathname() == self.program_path {
return Ordering::Less;
};
d1.unit_count().cmp(&d2.unit_count())
});
dwarfs
}
fn find_range(&self, addr: RelocatedAddress) -> Option<&(PathBuf, RegionRange)> {
self.ranges
.binary_search_by(|(_, range)| {
if addr >= range.from && addr <= range.to {
Ordering::Equal
} else if range.from > addr {
Ordering::Greater
} else {
Ordering::Less
}
})
.ok()
.map(|idx| &self.ranges[idx])
}
/// Return debug information that describes .text section determined by given address.
///
/// # Arguments
///
/// * `addr`: memory address that determine .text section.
pub fn find_by_addr(&self, addr: RelocatedAddress) -> Option<&DebugInformation> {
let (path, _) = self.find_range(addr)?;
self.files.get(path)
}
/// Return debug information extracted from file.
///
/// # Arguments
///
/// * `path`: already parsed file contains debug information.
pub fn find_by_file(&self, path: &Path) -> Option<&DebugInformation> {
self.files.get(path)
}
/// Calculate virtual memory region to which the address belongs and return
/// this region offset.
///
/// # Arguments
///
/// * `pc`: address for determine VAS region.
pub fn find_mapping_offset(&self, addr: RelocatedAddress) -> Option<usize> {
let (path, _) = self.find_range(addr)?;
self.mappings.get(path).copied()
}
/// Return offset of mapped memory region.
///
/// # Arguments
///
/// * `dwarf`: debug information for determine memory region.
pub fn find_mapping_offset_for_file(&self, dwarf: &DebugInformation) -> Option<usize> {
self.mappings.get(dwarf.pathname()).copied()
}
/// Find main executable object debug information.
pub fn find_main_program_dwarf(&self) -> Option<&DebugInformation> {
self.files.get(&self.program_path)
}
/// Create new [`DwarfRegistry`] with same dwarf info.
///
/// # Arguments
///
/// * `new_pid`: new process pid
pub fn extend(&self, new_pid: Pid) -> Self {
Self {
pid: new_pid,
program_path: self.program_path.clone(),
files: self.files.clone(),
// mappings and ranges must be redefined
ranges: vec![],
mappings: HashMap::default(),
}
}
/// Return an ordered list of mapped regions (main executable region at first place).
pub fn dump(&self) -> Vec<RegionInfo> {
let mut regions: Vec<_> = self
.files
.iter()
.map(|(path, debug_info)| {
let file_range = self.ranges.iter().find(|(p, _)| path == p);
RegionInfo {
has_debug_info: debug_info.has_debug_info(),
path: path.clone(),
range: file_range.map(|(_, range)| range.clone()),
}
})
.collect();
regions.sort_unstable_by(|i1, i2| {
if i1.path == self.program_path {
return Ordering::Less;
};
if i2.path == self.program_path {
return Ordering::Greater;
}
i1.path.cmp(&i2.path)
});
regions
}
}