Skip to content

Commit 12b9989

Browse files
feat: inherit process mapping on forks
1 parent fa0bfcb commit 12b9989

1 file changed

Lines changed: 121 additions & 1 deletion

File tree

‎src/executor/wall_time/profiler/perf/parse_perf_file.rs‎

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::loaded_module::LoadedModule;
1+
use super::loaded_module::{LoadedModule, ProcessLoadedModule};
22
use super::module_symbols::ModuleSymbols;
33
use super::unwind_data::unwind_data_from_elf;
44
use crate::prelude::*;
@@ -57,12 +57,23 @@ pub fn parse_for_memmap2<P: AsRef<Path>>(
5757
continue;
5858
};
5959

60+
// Thread creation also emits FORK, which share parent's address space, so nothing to do
61+
if fork_record.ppid == fork_record.pid {
62+
continue;
63+
}
64+
6065
if pid_filter.add_child_if_parent_tracked(fork_record.ppid, fork_record.pid) {
6166
trace!(
6267
"Fork: Tracking child PID {} from parent PID {}",
6368
fork_record.pid, fork_record.ppid
6469
);
6570
}
71+
72+
inherit_parent_mappings(
73+
&mut loaded_modules_by_path,
74+
fork_record.ppid,
75+
fork_record.pid,
76+
);
6677
}
6778
RecordType::MMAP2 => {
6879
let Ok(parsed_record) = record.parse() else {
@@ -134,6 +145,37 @@ impl PidFilter {
134145
}
135146
}
136147

148+
/// Copy every module the parent pid has mounted onto the child pid.
149+
///
150+
/// Forked processes inherit their parent's memory mappings, but there will not be any MMAP2 record
151+
/// in the perf data since the mapping has already happened.
152+
fn inherit_parent_mappings(
153+
loaded_modules_by_path: &mut HashMap<PathBuf, LoadedModule>,
154+
ppid: pid_t,
155+
pid: pid_t,
156+
) {
157+
use std::collections::hash_map::Entry;
158+
159+
for loaded_module in loaded_modules_by_path.values_mut() {
160+
let inherited =
161+
loaded_module
162+
.process_loaded_modules
163+
.get(&ppid)
164+
.map(|p| ProcessLoadedModule {
165+
symbols_load_bias: p.symbols_load_bias,
166+
process_unwind_data: p.process_unwind_data.clone(),
167+
});
168+
let Some(inherited) = inherited else {
169+
continue;
170+
};
171+
// Only insert if the child has no entry yet; an existing entry came from
172+
// the child's own MMAP2 and is authoritative.
173+
if let Entry::Vacant(slot) = loaded_module.process_loaded_modules.entry(pid) {
174+
slot.insert(inherited);
175+
}
176+
}
177+
}
178+
137179
/// Process a single MMAP2 record and add it to the symbols and unwind data maps
138180
fn process_mmap2_record(
139181
record: linux_perf_data::linux_perf_event_reader::Mmap2Record,
@@ -223,3 +265,81 @@ fn process_mmap2_record(
223265
}
224266
};
225267
}
268+
269+
#[cfg(test)]
270+
mod tests {
271+
use super::*;
272+
273+
fn make_module_with_parent(ppid: pid_t, load_bias: u64) -> LoadedModule {
274+
let mut m = LoadedModule::default();
275+
m.process_loaded_modules.insert(
276+
ppid,
277+
ProcessLoadedModule {
278+
symbols_load_bias: Some(load_bias),
279+
process_unwind_data: None,
280+
},
281+
);
282+
m
283+
}
284+
285+
#[test]
286+
fn inherit_parent_mappings_copies_each_module_to_child() {
287+
let mut modules: HashMap<PathBuf, LoadedModule> = HashMap::new();
288+
modules.insert(
289+
PathBuf::from("/lib/libpython.so"),
290+
make_module_with_parent(100, 0xdead),
291+
);
292+
modules.insert(
293+
PathBuf::from("/lib/libc.so"),
294+
make_module_with_parent(100, 0xbeef),
295+
);
296+
297+
inherit_parent_mappings(&mut modules, 100, 200);
298+
299+
for (path, m) in &modules {
300+
let child = m
301+
.process_loaded_modules
302+
.get(&200)
303+
.unwrap_or_else(|| panic!("missing child entry for {path:?}"));
304+
let parent = m.process_loaded_modules.get(&100).unwrap();
305+
assert_eq!(child.symbols_load_bias, parent.symbols_load_bias);
306+
}
307+
}
308+
309+
#[test]
310+
fn inherit_parent_mappings_skips_modules_without_parent() {
311+
let mut modules: HashMap<PathBuf, LoadedModule> = HashMap::new();
312+
modules.insert(PathBuf::from("/lib/foo.so"), LoadedModule::default());
313+
314+
inherit_parent_mappings(&mut modules, 100, 200);
315+
316+
assert!(
317+
modules[&PathBuf::from("/lib/foo.so")]
318+
.process_loaded_modules
319+
.is_empty()
320+
);
321+
}
322+
323+
#[test]
324+
fn inherit_parent_mappings_does_not_overwrite_existing_child_entry() {
325+
let mut modules: HashMap<PathBuf, LoadedModule> = HashMap::new();
326+
let mut m = make_module_with_parent(100, 0xdead);
327+
// Child already has its own (post-exec) mapping at a different bias.
328+
m.process_loaded_modules.insert(
329+
200,
330+
ProcessLoadedModule {
331+
symbols_load_bias: Some(0xcafe),
332+
process_unwind_data: None,
333+
},
334+
);
335+
modules.insert(PathBuf::from("/lib/libpython.so"), m);
336+
337+
inherit_parent_mappings(&mut modules, 100, 200);
338+
339+
let child = modules[&PathBuf::from("/lib/libpython.so")]
340+
.process_loaded_modules
341+
.get(&200)
342+
.unwrap();
343+
assert_eq!(child.symbols_load_bias, Some(0xcafe));
344+
}
345+
}

0 commit comments

Comments
 (0)