44use  std:: collections:: BTreeMap ; 
55use  std:: path:: Path ; 
66
7- use  anyhow:: { anyhow,  Error ,  Result } ; 
7+ use  anyhow:: { anyhow,  bail,  Error ,  Result } ; 
8+ use  debuggable_module:: debuginfo:: { DebugInfo ,  Function } ; 
89use  debuggable_module:: load_module:: LoadModule ; 
910use  debuggable_module:: loader:: Loader ; 
1011use  debuggable_module:: path:: FilePath ; 
1112use  debuggable_module:: windows:: WindowsModule ; 
12- use  debuggable_module:: Offset ; 
13+ use  debuggable_module:: { Module ,   Offset } ; 
1314use  debugger:: { BreakpointId ,  BreakpointType ,  DebugEventHandler ,  Debugger ,  ModuleLoadInfo } ; 
1415
1516use  crate :: allowlist:: TargetAllowList ; 
1617use  crate :: binary:: { self ,  BinaryCoverage } ; 
1718
19+ // For a new module image, we defer setting coverage breakpoints until exit from one of these 
20+ // functions (when present). This avoids breaking hotpatching routines in the ASan interceptor 
21+ // initializers. 
22+ // 
23+ // We will use these constants with a prefix comparison. Only check up to the first open paren to 
24+ // avoid false negatives if the demangled representation of parameters ever varies. 
25+ const  PROCESS_IMAGE_DEFERRAL_TRIGGER :  & str  = "__asan::AsanInitInternal(" ; 
26+ const  LIBRARY_IMAGE_DEFERRAL_TRIGGER :  & str  = "DllMain(" ; 
27+ 
1828pub  struct  WindowsRecorder < ' data >  { 
1929    allowlist :  TargetAllowList , 
2030    breakpoints :  Breakpoints , 
31+     deferred_breakpoints :  BTreeMap < BreakpointId ,  ( Breakpoint ,  DeferralState ) > , 
2132    pub  coverage :  BinaryCoverage , 
2233    loader :  & ' data  Loader , 
23-     modules :  BTreeMap < FilePath ,  WindowsModule < ' data > > , 
34+     modules :  BTreeMap < FilePath ,  ( WindowsModule < ' data > ,   DebugInfo ) > , 
2435    pub  stop_error :  Option < Error > , 
2536} 
2637
2738impl < ' data >  WindowsRecorder < ' data >  { 
2839    pub  fn  new ( loader :  & ' data  Loader ,  allowlist :  TargetAllowList )  -> Self  { 
2940        let  breakpoints = Breakpoints :: default ( ) ; 
41+         let  deferred_breakpoints = BTreeMap :: new ( ) ; 
3042        let  coverage = BinaryCoverage :: default ( ) ; 
3143        let  modules = BTreeMap :: new ( ) ; 
3244        let  stop_error = None ; 
3345
3446        Self  { 
3547            allowlist, 
3648            breakpoints, 
49+             deferred_breakpoints, 
3750            coverage, 
3851            loader, 
3952            modules, 
@@ -66,11 +79,59 @@ impl<'data> WindowsRecorder<'data> {
6679        self . insert_module ( dbg,  module) 
6780    } 
6881
69-     fn  try_on_breakpoint ( & mut  self ,  _dbg :  & mut  Debugger ,  id :  BreakpointId )  -> Result < ( ) >  { 
70-         let  breakpoint = self 
71-             . breakpoints 
72-             . remove ( id) 
73-             . ok_or_else ( || anyhow ! ( "stopped on dangling breakpoint" ) ) ?; 
82+     fn  try_on_breakpoint ( & mut  self ,  dbg :  & mut  Debugger ,  id :  BreakpointId )  -> Result < ( ) >  { 
83+         if  let  Some ( ( trigger,  state) )  = self . deferred_breakpoints . remove ( & id)  { 
84+             match  state { 
85+                 DeferralState :: NotEntered  => { 
86+                     debug ! ( 
87+                         "hit trigger breakpoint (not-entered) for deferred coverage breakpoints" 
88+                     ) ; 
89+ 
90+                     // Find the return address. 
91+                     let  frame = dbg. get_current_frame ( ) ?; 
92+                     let  ret = frame. return_address ( ) ; 
93+                     let  id = dbg. new_address_breakpoint ( ret,  BreakpointType :: OneTime ) ?; 
94+ 
95+                     // Update the state for this deferral to set module coverage breakpoints on ret. 
96+                     let  thread_id = dbg. get_current_thread_id ( ) ; 
97+                     let  state = DeferralState :: PendingReturn  {  thread_id } ; 
98+                     self . deferred_breakpoints . insert ( id,  ( trigger,  state) ) ; 
99+                 } 
100+                 DeferralState :: PendingReturn  {  thread_id }  => { 
101+                     debug ! ( 
102+                         "hit trigger breakpoint (pending-return) for deferred coverage breakpoints" 
103+                     ) ; 
104+ 
105+                     if  dbg. get_current_thread_id ( )  == thread_id { 
106+                         debug ! ( "correct thread hit pending-return trigger breakpoint" ) ; 
107+ 
108+                         // We've returned from the trigger function, and on the same thread. 
109+                         // 
110+                         // It's safe to set coverage breakpoints. 
111+                         self . set_module_breakpoints ( dbg,  trigger. module ) ?; 
112+                     }  else  { 
113+                         warn ! ( "wrong thread saw pending-return trigger breakpoint, resetting" ) ; 
114+ 
115+                         // Hit a ret breakpoint, but on the wrong thread. Reset it so the correct 
116+                         // thread has a chance to see it. 
117+                         // 
118+                         // We only defer breakpoints in image initialization code, so we don't 
119+                         // expect to reach this code in practice. 
120+                         let  id = trigger. set ( dbg) ?; 
121+                         self . deferred_breakpoints . insert ( id,  ( trigger,  state) ) ; 
122+                     } 
123+                 } 
124+             } 
125+ 
126+             return  Ok ( ( ) ) ; 
127+         } 
128+ 
129+         let  breakpoint = self . breakpoints . remove ( id) ; 
130+ 
131+         let  Some ( breakpoint)  = breakpoint else  { 
132+             let  stack = dbg. get_current_stack ( ) ?; 
133+             bail ! ( "stopped on dangling breakpoint, debuggee stack:\n {}" ,  stack) ; 
134+         } ; 
74135
75136        let  coverage = self 
76137            . coverage 
@@ -91,6 +152,11 @@ impl<'data> WindowsRecorder<'data> {
91152    fn  insert_module ( & mut  self ,  dbg :  & mut  Debugger ,  module :  & ModuleLoadInfo )  -> Result < ( ) >  { 
92153        let  path = FilePath :: new ( module. path ( ) . to_string_lossy ( ) ) ?; 
93154
155+         if  self . modules . contains_key ( & path)  { 
156+             warn ! ( "module coverage already initialized, skipping" ) ; 
157+             return  Ok ( ( ) ) ; 
158+         } 
159+ 
94160        if  !self . allowlist . modules . is_allowed ( & path)  { 
95161            debug ! ( "not inserting denylisted module: {path}" ) ; 
96162            return  Ok ( ( ) ) ; 
@@ -103,72 +169,123 @@ impl<'data> WindowsRecorder<'data> {
103169            return  Ok ( ( ) ) ; 
104170        } ; 
105171
106-         let  coverage = binary:: find_coverage_sites ( & module,  & self . allowlist ) ?; 
172+         let  debuginfo = module. debuginfo ( ) ?; 
173+         self . modules . insert ( path. clone ( ) ,  ( module,  debuginfo) ) ; 
174+ 
175+         self . set_or_defer_module_breakpoints ( dbg,  path) ?; 
176+ 
177+         Ok ( ( ) ) 
178+     } 
179+ 
180+     fn  set_or_defer_module_breakpoints ( 
181+         & mut  self , 
182+         dbg :  & mut  Debugger , 
183+         path :  FilePath , 
184+     )  -> Result < ( ) >  { 
185+         let  ( _module,  debuginfo)  = & self . modules [ & path] ; 
186+ 
187+         // For borrowck. 
188+         let  mut  trigger = None ; 
189+ 
190+         for  function in  debuginfo. functions ( )  { 
191+             // Called on process startup. 
192+             if  function. name . starts_with ( PROCESS_IMAGE_DEFERRAL_TRIGGER )  { 
193+                 trigger = Some ( function. clone ( ) ) ; 
194+                 break ; 
195+             } 
196+ 
197+             // Called on shared library load. 
198+             if  function. name . starts_with ( LIBRARY_IMAGE_DEFERRAL_TRIGGER )  { 
199+                 trigger = Some ( function. clone ( ) ) ; 
200+                 break ; 
201+             } 
202+         } 
203+ 
204+         if  let  Some ( trigger)  = trigger { 
205+             debug ! ( "deferring coverage breakpoints for module {}" ,  path) ; 
206+             self . defer_module_breakpoints ( dbg,  path,  trigger) 
207+         }  else  { 
208+             debug ! ( 
209+                 "immediately setting coverage breakpoints for module {}" , 
210+                 path
211+             ) ; 
212+             self . set_module_breakpoints ( dbg,  path) 
213+         } 
214+     } 
215+ 
216+     fn  defer_module_breakpoints ( 
217+         & mut  self , 
218+         dbg :  & mut  Debugger , 
219+         path :  FilePath , 
220+         trigger :  Function , 
221+     )  -> Result < ( ) >  { 
222+         let  state = DeferralState :: NotEntered ; 
223+         let  entry_breakpoint = Breakpoint :: new ( path,  trigger. offset ) ; 
224+         let  id = entry_breakpoint. set ( dbg) ?; 
225+ 
226+         self . deferred_breakpoints 
227+             . insert ( id,  ( entry_breakpoint,  state) ) ; 
228+ 
229+         Ok ( ( ) ) 
230+     } 
231+ 
232+     fn  set_module_breakpoints ( & mut  self ,  dbg :  & mut  Debugger ,  path :  FilePath )  -> Result < ( ) >  { 
233+         let  ( module,  _)  = & self . modules [ & path] ; 
234+         let  coverage = binary:: find_coverage_sites ( module,  & self . allowlist ) ?; 
107235
108236        for  offset in  coverage. as_ref ( ) . keys ( ) . copied ( )  { 
109237            let  breakpoint = Breakpoint :: new ( path. clone ( ) ,  offset) ; 
110238            self . breakpoints . set ( dbg,  breakpoint) ?; 
111239        } 
112240
113-         self . coverage . modules . insert ( path. clone ( ) ,  coverage) ; 
241+         let  count = coverage. offsets . len ( ) ; 
242+         debug ! ( "set {} breakpoints for module {}" ,  count,  path) ; 
114243
115-         self . modules . insert ( path,  module ) ; 
244+         self . coverage . modules . insert ( path,  coverage ) ; 
116245
117246        Ok ( ( ) ) 
118247    } 
119248} 
120249
121250#[ derive( Debug ,  Default ) ]  
122251struct  Breakpoints  { 
123-     id_to_offset :  BTreeMap < BreakpointId ,  Offset > , 
124-     offset_to_breakpoint :  BTreeMap < Offset ,  Breakpoint > , 
252+     id_to_breakpoint :  BTreeMap < BreakpointId ,  Breakpoint > , 
125253} 
126254
127255impl  Breakpoints  { 
128256    pub  fn  set ( & mut  self ,  dbg :  & mut  Debugger ,  breakpoint :  Breakpoint )  -> Result < ( ) >  { 
129-         if  self . is_set ( & breakpoint)  { 
130-             return  Ok ( ( ) ) ; 
131-         } 
132- 
133-         self . write ( dbg,  breakpoint) 
134-     } 
135- 
136-     // Unguarded action that ovewrites both the target process address space and our index 
137-     // of known breakpoints. Callers must use `set()`, which avoids redundant breakpoint 
138-     // setting. 
139-     fn  write ( & mut  self ,  dbg :  & mut  Debugger ,  breakpoint :  Breakpoint )  -> Result < ( ) >  { 
140-         // The `debugger` crates tracks loaded modules by base name. If a path or file 
141-         // name is used, software breakpoints will not be written. 
142-         let  name = Path :: new ( breakpoint. module . base_name ( ) ) ; 
143-         let  id = dbg. new_rva_breakpoint ( name,  breakpoint. offset . 0 ,  BreakpointType :: OneTime ) ?; 
144- 
145-         self . id_to_offset . insert ( id,  breakpoint. offset ) ; 
146-         self . offset_to_breakpoint 
147-             . insert ( breakpoint. offset ,  breakpoint) ; 
148- 
257+         let  id = breakpoint. set ( dbg) ?; 
258+         self . id_to_breakpoint . insert ( id,  breakpoint) ; 
149259        Ok ( ( ) ) 
150260    } 
151261
152-     pub   fn   is_set ( & self ,   breakpoint :   & Breakpoint )  ->  bool   { 
153-          self . offset_to_breakpoint . contains_key ( & breakpoint . offset ) 
154-     } 
155- 
262+     /// Remove a registered  breakpoint from the index. 
263+      /// 
264+      /// This method should be called when a breakpoint is hit to retrieve its associated data. 
265+      /// It does NOT clear the actual breakpoint in the debugger engine. 
156266     pub  fn  remove ( & mut  self ,  id :  BreakpointId )  -> Option < Breakpoint >  { 
157-         let  offset = self . id_to_offset . remove ( & id) ?; 
158-         self . offset_to_breakpoint . remove ( & offset) 
267+         self . id_to_breakpoint . remove ( & id) 
159268    } 
160269} 
161270
162271#[ derive( Clone ,  Debug ) ]  
163272struct  Breakpoint  { 
164-     module :  FilePath , 
165-     offset :  Offset , 
273+     pub   module :  FilePath , 
274+     pub   offset :  Offset , 
166275} 
167276
168277impl  Breakpoint  { 
169278    pub  fn  new ( module :  FilePath ,  offset :  Offset )  -> Self  { 
170279        Self  {  module,  offset } 
171280    } 
281+ 
282+     pub  fn  set ( & self ,  dbg :  & mut  Debugger )  -> Result < BreakpointId >  { 
283+         // The `debugger` crates tracks loaded modules by base name. If a path or file 
284+         // name is used, software breakpoints will not be written. 
285+         let  name = Path :: new ( self . module . base_name ( ) ) ; 
286+         let  id = dbg. new_rva_breakpoint ( name,  self . offset . 0 ,  BreakpointType :: OneTime ) ?; 
287+         Ok ( id) 
288+     } 
172289} 
173290
174291impl < ' data >  DebugEventHandler  for  WindowsRecorder < ' data >  { 
@@ -193,3 +310,8 @@ impl<'data> DebugEventHandler for WindowsRecorder<'data> {
193310        } 
194311    } 
195312} 
313+ 
314+ enum  DeferralState  { 
315+     NotEntered , 
316+     PendingReturn  {  thread_id :  u64  } , 
317+ } 
0 commit comments