@@ -39,6 +39,7 @@ pub fn parse_for_memmap2<P: AsRef<Path>>(
3939 mut record_iter,
4040 } = PerfFileReader :: parse_pipe ( reader) ?;
4141
42+ // This loop relies on the events being in chronological order, which is guaranteed by the perf file format.
4243 while let Some ( record) = record_iter. next_record ( & mut perf_file) ? {
4344 let PerfFileRecord :: EventRecord { record, .. } = record else {
4445 continue ;
@@ -75,6 +76,30 @@ pub fn parse_for_memmap2<P: AsRef<Path>>(
7576 fork_record. pid ,
7677 ) ;
7778 }
79+ RecordType :: COMM => {
80+ // An execve() replaces the entire address space: the mappings inherited from the
81+ // parent on fork (and any pre-exec mappings of this pid) are no longer valid.
82+ // TODO(COD-1377): Manage timing in module mapping to improve symbolication.
83+ let Ok ( parsed_record) = record. parse ( ) else {
84+ continue ;
85+ } ;
86+
87+ let EventRecord :: Comm ( comm_record) = parsed_record else {
88+ continue ;
89+ } ;
90+
91+ if !comm_record. is_execve {
92+ continue ;
93+ }
94+
95+ if pid_filter. should_include ( comm_record. pid ) {
96+ trace ! (
97+ "Exec: Purging inherited mappings for PID {}" ,
98+ comm_record. pid
99+ ) ;
100+ purge_process_mappings ( & mut loaded_modules_by_path, comm_record. pid ) ;
101+ }
102+ }
78103 RecordType :: MMAP2 => {
79104 let Ok ( parsed_record) = record. parse ( ) else {
80105 continue ;
@@ -176,6 +201,13 @@ fn inherit_parent_mappings(
176201 }
177202}
178203
204+ /// Drop every mapping recorded for `pid` across all modules.
205+ fn purge_process_mappings ( loaded_modules_by_path : & mut HashMap < PathBuf , LoadedModule > , pid : pid_t ) {
206+ for loaded_module in loaded_modules_by_path. values_mut ( ) {
207+ loaded_module. process_loaded_modules . remove ( & pid) ;
208+ }
209+ }
210+
179211/// Process a single MMAP2 record and add it to the symbols and unwind data maps
180212fn process_mmap2_record (
181213 record : linux_perf_data:: linux_perf_event_reader:: Mmap2Record ,
@@ -319,4 +351,51 @@ mod tests {
319351 . unwrap ( ) ;
320352 assert_eq ! ( child. symbols_load_bias, Some ( 0xcafe ) ) ;
321353 }
354+
355+ #[ test]
356+ fn purge_process_mappings_removes_only_the_target_pid ( ) {
357+ // Reproduces the fork+exec collision: child 200 forked from bash (100) and inherited
358+ // bash's PIE mapping at 0xaaaaaaaa0000, then exec'd its own binary at the same base.
359+ let mut modules: HashMap < PathBuf , LoadedModule > = HashMap :: new ( ) ;
360+
361+ // Inherited-from-bash module that must be dropped on exec.
362+ let mut bash = make_module_with_parent ( 100 , 0xaaaaaaaa0000 ) ;
363+ bash. process_loaded_modules . insert (
364+ 200 ,
365+ ProcessLoadedModule {
366+ symbols_load_bias : Some ( 0xaaaaaaaa0000 ) ,
367+ process_unwind_data : None ,
368+ } ,
369+ ) ;
370+ modules. insert ( PathBuf :: from ( "/usr/bin/bash" ) , bash) ;
371+
372+ // The real post-exec binary, also based at 0xaaaaaaaa0000 for pid 200.
373+ modules. insert (
374+ PathBuf :: from ( "/cpp/build/fractal_main" ) ,
375+ make_module_with_parent ( 200 , 0xaaaaaaaa0000 ) ,
376+ ) ;
377+
378+ purge_process_mappings ( & mut modules, 200 ) ;
379+
380+ // Pid 200 is gone from every module...
381+ assert ! (
382+ !modules[ & PathBuf :: from( "/usr/bin/bash" ) ]
383+ . process_loaded_modules
384+ . contains_key( & 200 )
385+ ) ;
386+ assert ! (
387+ !modules[ & PathBuf :: from( "/cpp/build/fractal_main" ) ]
388+ . process_loaded_modules
389+ . contains_key( & 200 )
390+ ) ;
391+ // ...but bash's own pid 100 mapping is untouched.
392+ assert_eq ! (
393+ modules[ & PathBuf :: from( "/usr/bin/bash" ) ]
394+ . process_loaded_modules
395+ . get( & 100 )
396+ . unwrap( )
397+ . symbols_load_bias,
398+ Some ( 0xaaaaaaaa0000 )
399+ ) ;
400+ }
322401}
0 commit comments