3
3
4
4
#![ allow( clippy:: single_match) ]
5
5
6
- use std:: { num:: NonZeroU64 , path:: Path } ;
6
+ use std:: { io , num:: NonZeroU64 , path:: Path } ;
7
7
8
- use anyhow:: Result ;
8
+ use anyhow:: { format_err , Result } ;
9
9
use log:: { debug, error, trace} ;
10
10
use rand:: { thread_rng, Rng } ;
11
- use win_util:: { last_os_error , process} ;
11
+ use win_util:: process;
12
12
use winapi:: {
13
- shared:: minwindef:: { DWORD , LPCVOID } ,
13
+ shared:: {
14
+ minwindef:: { DWORD , LPCVOID } ,
15
+ winerror:: ERROR_ACCESS_DENIED ,
16
+ } ,
14
17
um:: {
15
18
processthreadsapi:: { ResumeThread , SuspendThread } ,
16
19
winbase:: Wow64SuspendThread ,
@@ -31,55 +34,117 @@ pub(crate) enum StepState {
31
34
SingleStep ,
32
35
}
33
36
37
+ #[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
38
+ enum ThreadState {
39
+ Runnable ,
40
+ Suspended ,
41
+ Exited ,
42
+ }
43
+
34
44
struct ThreadInfo {
35
45
#[ allow( unused) ]
36
46
id : u32 ,
37
47
handle : HANDLE ,
38
- suspended : bool ,
48
+ state : ThreadState ,
39
49
wow64 : bool ,
40
50
}
41
51
52
+ const SUSPEND_RESUME_ERROR_CODE : DWORD = -1i32 as DWORD ;
53
+
42
54
impl ThreadInfo {
43
55
fn new ( id : u32 , handle : HANDLE , wow64 : bool ) -> Self {
44
56
ThreadInfo {
45
57
id,
46
58
handle,
47
59
wow64,
48
- suspended : false ,
60
+ state : ThreadState :: Runnable ,
49
61
}
50
62
}
51
63
52
- fn resume_thread ( & mut self ) -> Result < ( ) > {
53
- if ! self . suspended {
54
- return Ok ( ( ) ) ;
64
+ fn resume_thread ( & mut self ) -> Result < ThreadState > {
65
+ if self . state == ThreadState :: Runnable {
66
+ return Ok ( self . state ) ;
55
67
}
56
68
57
- let suspend_count = unsafe { ResumeThread ( self . handle ) } ;
58
- if suspend_count == ( -1i32 as DWORD ) {
59
- Err ( last_os_error ( ) )
60
- } else {
61
- self . suspended = false ;
62
- Ok ( ( ) )
69
+ let prev_suspend_count = unsafe { ResumeThread ( self . handle ) } ;
70
+
71
+ match prev_suspend_count {
72
+ SUSPEND_RESUME_ERROR_CODE => {
73
+ let os_error = io:: Error :: last_os_error ( ) ;
74
+
75
+ if Self :: is_os_error_from_exited_thread ( & os_error) ? {
76
+ self . state = ThreadState :: Exited ;
77
+ } else {
78
+ return Err ( os_error. into ( ) ) ;
79
+ }
80
+ }
81
+ 0 => {
82
+ // No-op: thread was runnable, and is still runnable.
83
+ self . state = ThreadState :: Runnable ;
84
+ }
85
+ 1 => {
86
+ // Was suspended, now runnable.
87
+ self . state = ThreadState :: Runnable ;
88
+ }
89
+ _ => {
90
+ // Previous suspend count > 1. Was suspended, still is.
91
+ self . state = ThreadState :: Suspended ;
92
+ }
63
93
}
94
+
95
+ Ok ( self . state )
64
96
}
65
97
66
- fn suspend_thread ( & mut self ) -> Result < ( ) > {
67
- if self . suspended {
68
- return Ok ( ( ) ) ;
98
+ fn suspend_thread ( & mut self ) -> Result < ThreadState > {
99
+ if self . state == ThreadState :: Suspended {
100
+ return Ok ( self . state ) ;
69
101
}
70
102
71
- let suspend_count = if self . wow64 {
103
+ let prev_suspend_count = if self . wow64 {
72
104
unsafe { Wow64SuspendThread ( self . handle ) }
73
105
} else {
74
106
unsafe { SuspendThread ( self . handle ) }
75
107
} ;
76
108
77
- if suspend_count == ( -1i32 as DWORD ) {
78
- Err ( last_os_error ( ) )
79
- } else {
80
- self . suspended = true ;
81
- Ok ( ( ) )
109
+ match prev_suspend_count {
110
+ SUSPEND_RESUME_ERROR_CODE => {
111
+ let os_error = io:: Error :: last_os_error ( ) ;
112
+
113
+ if Self :: is_os_error_from_exited_thread ( & os_error) ? {
114
+ self . state = ThreadState :: Exited ;
115
+ } else {
116
+ return Err ( os_error. into ( ) ) ;
117
+ }
118
+ }
119
+ _ => {
120
+ // Suspend count was incremented. Even if the matched value is 0, it means
121
+ // the current suspend count is 1, and the thread is suspended.
122
+ self . state = ThreadState :: Suspended ;
123
+ }
82
124
}
125
+
126
+ Ok ( self . state )
127
+ }
128
+
129
+ fn is_os_error_from_exited_thread ( os_error : & io:: Error ) -> Result < bool > {
130
+ let raw_os_error = os_error
131
+ . raw_os_error ( )
132
+ . ok_or_else ( || format_err ! ( "OS error missing raw error" ) ) ?;
133
+
134
+ let exited = match raw_os_error as DWORD {
135
+ ERROR_ACCESS_DENIED => {
136
+ // Assume, as a debugger, we always have the `THREAD_SUSPEND_RESUME`
137
+ // access right, and thus we should interpret this error to mean that
138
+ // the thread has exited.
139
+ //
140
+ // This means we are observing a race between OS-level thread exit and
141
+ // the (pending) debug event.
142
+ true
143
+ }
144
+ _ => false ,
145
+ } ;
146
+
147
+ Ok ( exited)
83
148
}
84
149
}
85
150
@@ -104,6 +169,7 @@ pub struct Target {
104
169
sym_initialize_state : SymInitalizeState ,
105
170
exited : bool ,
106
171
172
+ // Map of thread ID to thread info.
107
173
thread_info : fnv:: FnvHashMap < DWORD , ThreadInfo > ,
108
174
109
175
// We cache the current thread context for possible repeated queries and modifications.
@@ -118,7 +184,7 @@ pub struct Target {
118
184
// Breakpoints that are not yet resolved to a virtual address, so either an RVA or symbol.
119
185
unresolved_breakpoints : Vec < UnresolvedBreakpoint > ,
120
186
121
- // Map of thread to stepping state (e.g. breakpoint address to restore breakpoints)
187
+ // Map of thread ID to stepping state (e.g. breakpoint address to restore breakpoints)
122
188
single_step : fnv:: FnvHashMap < DWORD , StepState > ,
123
189
124
190
// When stepping after hitting a breakpoint, we need to restore the breakpoint.
0 commit comments