Repository: microsoft/onefuzz
File: src/agent/coverage/src/record/windows.rs (Rust)

Code before:
                        //
                        // We only defer breakpoints in image initialization code, so we don't
                        // expect to reach this code in practice.
                        let id = trigger.set(dbg)?;
                        self.deferred_breakpoints.insert(id, (trigger, state));
                    }
                }
            }

            return Ok(());
        }

        let breakpoint = self.breakpoints.remove(id);

        let Some(breakpoint) = breakpoint else {
            let stack = dbg.get_current_stack()?;
            bail!("stopped on dangling breakpoint, debuggee stack:\n{}", stack);
        };

        let coverage = self
            .coverage
            .modules
            .get_mut(&breakpoint.module)
            .ok_or_else(|| anyhow!("coverage not initialized for module: {}", breakpoint.module))?;

        coverage.increment(breakpoint.offset);

        Ok(())
    }

    fn stop(&mut self, dbg: &mut Debugger, stop_error: impl Into<Option<Error>>) {
        self.stop_error = stop_error.into();
        dbg.quit_debugging();
    }

    fn insert_module(&mut self, dbg: &mut Debugger, module: &ModuleLoadInfo) -> Result<()> {
        let path = FilePath::new(module.path().to_string_lossy())?;

        if !self.allowlist.modules.is_allowed(&path) {
            debug!("not inserting denylisted module: {path}");
            return Ok(());
        }

        let module = if let Ok(m) = WindowsModule::load(self.loader, path.clone()) {
            m
        } else {
            debug!("skipping undebuggable module: {path}");
            return Ok(());
        };

        let debuginfo = module.debuginfo()?;

Review comment (bug):
I added the call stack to the error here so make it easier to debug / identify the interception bug if it pops up again.

Interceptor init-breaking errors will look something like:
```
Error: stopped on dangling breakpoint, debuggee stack:
fuzz!__interception::InterceptionFailed+0x0
fuzz!__interception::GetInstructionSize+0x5d7
fuzz!__interception::OverrideFunctionWithHotPatch+0x22
fuzz!__interception::OverrideFunction+0x27
fuzz!InitializeCommonInterceptors+0x1a
fuzz!__asan::InitializeAsanInterceptors+0x32
fuzz!__asan::AsanInitInternal+0x14e
ntdll!RtlActivateActivationContextUnsafeFast+0x11d
ntdll!RtlActivateActivationContextUnsafeFast+0x1ff
ntdll!LdrInitShimEngineDynamic+0x3b88
ntdll!LdrInitializeThunk+0x1db
ntdll!LdrInitializeThunk+0x63
ntdll!LdrInitializeThunk+0xe
``` 

Code after:
                        // We've returned from the trigger function, and on the same thread.
                        //
                        // It's safe to set coverage breakpoints.
                        self.set_module_breakpoints(dbg, trigger.module)?;
                    } else {
                        warn!("wrong thread saw pending-return trigger breakpoint, resetting");

                        // Hit a ret breakpoint, but on the wrong thread. Reset it so the correct
                        // thread has a chance to see it.
                        //
                        // We only defer breakpoints in image initialization code, so we don't
                        // expect to reach this code in practice.
                        let id = trigger.set(dbg)?;
                        self.deferred_breakpoints.insert(id, (trigger, state));
                    }
                }
            }

            return Ok(());
        }

        let breakpoint = self.breakpoints.remove(id);

        let Some(breakpoint) = breakpoint else {
            let stack = dbg.get_current_stack()?;
            bail!("stopped on dangling breakpoint, debuggee stack:\n{}", stack);
        };

        let coverage = self
            .coverage
            .modules
            .get_mut(&breakpoint.module)
            .ok_or_else(|| anyhow!("coverage not initialized for module: {}", breakpoint.module))?;

        coverage.increment(breakpoint.offset);

        Ok(())
    }

    fn stop(&mut self, dbg: &mut Debugger, stop_error: impl Into<Option<Error>>) {
        self.stop_error = stop_error.into();
        dbg.quit_debugging();
    }

    fn insert_module(&mut self, dbg: &mut Debugger, module: &ModuleLoadInfo) -> Result<()> {
        let path = FilePath::new(module.path().to_string_lossy())?;

        if self.modules.contains_key(&path) {
            warn!("module coverage already initialized, skipping");
            return Ok(());
        }