OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 The Native Client Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 #include "debugger/core/debuggee_thread.h" | |
5 #include <assert.h> | |
6 #include "debugger/core/debug_api.h" | |
7 #include "debugger/core/debug_breakpoint.h" | |
8 #include "debugger/core/debug_event.h" | |
9 #include "debugger/core/debug_logger.h" | |
10 #include "debugger/core/debuggee_iprocess.h" | |
11 | |
12 #pragma warning(disable : 4996) // Disable sscanf warning. | |
13 | |
14 namespace { | |
15 const size_t kMaxStringSize = 32 * 1024; | |
16 const char* kNexeUuid = | |
17 "{7AA7C9CF-89EC-4ed3-8DAD-6DC84302AB11} -v 1 -event NaClThreadStart"; | |
18 } | |
19 | |
20 namespace debug { | |
21 DebuggeeThread::DebuggeeThread(int id, | |
22 HANDLE handle, | |
23 IDebuggeeProcess* parent_process) | |
24 : id_(id), | |
25 handle_(handle), | |
26 parent_process_(*parent_process), | |
27 state_(kHalted), | |
28 exit_code_(0), | |
29 triggered_breakpoint_addr_(NULL), | |
30 is_nacl_app_thread_(false) { | |
31 assert(NULL != parent_process); | |
32 } | |
33 | |
34 DebugAPI& DebuggeeThread::debug_api() { | |
35 return parent_process().debug_api(); | |
36 } | |
37 | |
38 void DebuggeeThread::SetState(State new_state) { | |
39 DBG_LOG("TR03.01", | |
40 "msg='DebuggeeThread::SetState' thread_id=%d old_state=%s" | |
41 " new_state=%s", | |
42 id(), | |
43 GetStateName(state_), | |
44 GetStateName(new_state)); | |
45 state_ = new_state; | |
46 } | |
47 | |
48 bool DebuggeeThread::IsHalted() const { | |
49 return (kHalted == state_); | |
50 } | |
51 | |
52 void DebuggeeThread::OnOutputDebugString(DebugEvent* debug_event) { | |
53 DEBUG_EVENT de = debug_event->windows_debug_event(); | |
54 if (0 == debug_event->windows_debug_event().u.DebugString.fUnicode) { | |
55 size_t sz = de.u.DebugString.nDebugStringLength + 1; | |
56 size_t str_sz = min(kMaxStringSize, sz); | |
57 char* tmp = static_cast<char*>(malloc(str_sz)); | |
58 if (NULL != tmp) { | |
59 if (parent_process().ReadMemory( | |
60 de.u.DebugString.lpDebugStringData, | |
61 str_sz, | |
62 tmp)) { | |
63 tmp[str_sz - 1] = 0; | |
64 | |
65 DBG_LOG("TR03.02", "OutputDebugString=%s", tmp); | |
66 | |
67 if (strncmp(tmp, kNexeUuid, strlen(kNexeUuid)) == 0) { | |
68 /// This string is coming from sel_ldr. | |
69 is_nacl_app_thread_ = true; | |
70 // Here we are passing pointers from sel_ldr to the debugger. | |
71 // One might think that we can't use them because they are | |
72 // valid in sel_ldr address space only. This is not true. | |
73 // ::ReadProcessMemory can be used with these pointers. | |
74 // Note: these pointers are not untrusted NaCl, they are | |
75 // native windows flat pointers. | |
76 void* nexe_mem_base = NULL; | |
77 void* nexe_entry_point = NULL; | |
78 sscanf(tmp + strlen(kNexeUuid), // NOLINT | |
79 " -mb %p -ep %p", // %p because size is different on 32bit | |
80 &nexe_mem_base, // and 64-bit versions of windows. | |
81 &nexe_entry_point); | |
82 parent_process().set_nexe_mem_base(nexe_mem_base); | |
83 parent_process().set_nexe_entry_point(nexe_entry_point); | |
84 | |
85 debug_event->set_nacl_debug_event_code( | |
86 DebugEvent::kThreadIsAboutToStart); | |
87 DBG_LOG("TR03.03", | |
88 "NaClThreadStart mem_base=%p entry_point=%p thread_id=%d", | |
89 nexe_mem_base, | |
90 nexe_entry_point, | |
91 id()); | |
92 } | |
93 } | |
94 free(tmp); | |
95 } | |
96 } | |
97 } | |
98 | |
99 // Here's what we have to do when we hit our breakpoint: | |
100 // a) recover original code at breakpoint adddress. | |
101 // b) return IP to the beginning of instruction (simple decrement work). | |
102 void DebuggeeThread::OnBreakpoint(DebugEvent* debug_event) { | |
103 DEBUG_EVENT de = debug_event->windows_debug_event(); | |
104 void* ex_addr = de.u.Exception.ExceptionRecord.ExceptionAddress; | |
105 void* br_addr = ex_addr; | |
106 if (IsNaClAppThread()) | |
107 br_addr = parent_process().FromNexeToFlatAddress(ex_addr); | |
108 | |
109 Breakpoint* br = parent_process().GetBreakpoint(br_addr); | |
110 | |
111 DBG_LOG("TR03.04", | |
112 "msg='DebuggeeThread::OnBreakpoint' thread_id=%d ex_addr=%p" | |
113 " br_addr=0x%p our=%s", | |
114 id(), | |
115 ex_addr, | |
116 br_addr, | |
117 (NULL != br) ? "yes" : "no"); | |
118 | |
119 if (NULL != br) { | |
120 // it's our breakpoint. | |
121 triggered_breakpoint_addr_ = br->address(); | |
122 br->RecoverCodeAtBreakpoint(); | |
123 SetIP(reinterpret_cast<char*>(GetIP()) - 1); | |
124 } | |
125 } | |
126 | |
127 bool DebuggeeThread::Continue(ContinueOption option) { | |
128 if (!IsHalted()) { | |
129 DBG_LOG("WARN03.01", | |
130 "msg='DebuggeeThread::Continue(%s) called while thread was in an" | |
131 " incompatible state: %s' thread_id=%d", | |
132 GetContinueOptionName(option), | |
133 GetStateName(state_), | |
134 id()); | |
135 return false; | |
136 } | |
137 const DebugEvent& last_debug_event = parent_process().last_debug_event(); | |
138 int last_debug_event_id = | |
139 last_debug_event.windows_debug_event().dwDebugEventCode; | |
140 if ((EXIT_THREAD_DEBUG_EVENT == last_debug_event_id) || | |
141 (EXIT_PROCESS_DEBUG_EVENT == last_debug_event_id)) { | |
142 SetState(kDead); | |
143 return (TRUE == debug_api().ContinueDebugEvent(parent_process().id(), | |
144 id(), | |
145 DBG_CONTINUE)); | |
146 } | |
147 | |
148 if (NULL != triggered_breakpoint_addr_) { | |
149 void* flat_ip = GetIP(); | |
150 if (IsNaClAppThread()) | |
151 flat_ip = parent_process().FromNexeToFlatAddress(GetIP()); | |
152 | |
153 if (flat_ip == triggered_breakpoint_addr_) { | |
154 return ContinueFromBreakpoint(); | |
155 } else { | |
156 // Just in case user changed IP so that it's not pointing to | |
157 // triggered breakpoint, we need to: | |
158 // a) recover breakpoint | |
159 // b) make it not triggered | |
160 Breakpoint* br = | |
161 parent_process().GetBreakpoint(triggered_breakpoint_addr_); | |
162 if (NULL != br) | |
163 br->WriteBreakpointCode(); | |
164 triggered_breakpoint_addr_ = NULL; | |
165 } | |
166 } | |
167 if (kSingleStep == option) | |
168 EnableSingleStep(true); | |
169 | |
170 int flags = DBG_CONTINUE; | |
171 if (kContinueAndPassException == option) | |
172 flags = DBG_EXCEPTION_NOT_HANDLED; | |
173 | |
174 SetState(kRunning); | |
175 return (TRUE == debug_api().ContinueDebugEvent(parent_process().id(), | |
176 id(), | |
177 flags)); | |
178 } | |
179 | |
180 /// Implements first steps of 'Continue from breakpoint' algorithm, | |
181 /// described in |DebuggeeThread::OnDebugEvent| method. | |
182 bool DebuggeeThread::ContinueFromBreakpoint() { | |
183 SetState(kContinueFromBreakpoint); | |
184 EnableSingleStep(true); | |
185 return (TRUE == debug_api().ContinueDebugEvent(parent_process().id(), | |
186 id(), | |
187 DBG_CONTINUE)); | |
188 } | |
189 | |
190 void DebuggeeThread::OnSingleStep(DebugEvent* debug_event) { | |
191 if (kContinueFromBreakpoint == state_) { | |
192 if (NULL == triggered_breakpoint_addr_) { | |
193 DBG_LOG("ERR03.01", | |
194 "msg=OnSingleStepDueToContinueFromBreakpoint thread_id=%d" | |
195 " triggered_breakpoint_=NULL", | |
196 id()); | |
197 SetState(kHalted); | |
198 } else { | |
199 DBG_LOG("TR03.05", | |
200 "msg=OnSingleStepDueToContinueFromBreakpoint thread_id=%d" | |
201 " breakpoint_addr=0x%p", | |
202 id(), | |
203 triggered_breakpoint_addr_); | |
204 // We got here because thread was continuing from breakpoint. | |
205 Breakpoint* br = | |
206 parent_process().GetBreakpoint(triggered_breakpoint_addr_); | |
207 if (NULL != br) | |
208 br->WriteBreakpointCode(); | |
209 triggered_breakpoint_addr_ = NULL; | |
210 debug_api().ContinueDebugEvent(parent_process().id(), | |
211 id(), | |
212 DBG_CONTINUE); | |
213 SetState(kRunning); | |
214 } | |
215 } else { | |
216 SetState(kHalted); | |
217 } | |
218 } | |
219 | |
220 void DebuggeeThread::OnDebugEvent(DebugEvent* debug_event) { | |
221 DEBUG_EVENT de = debug_event->windows_debug_event(); | |
222 EnableSingleStep(false); | |
223 | |
224 // Thread expected 'SingleStep' exception due to 'continue from breakpoint' | |
225 // algorithm, but got something else. For example, it happens when breakpoint | |
226 // is set to instruction generating 'memory access violation' exception. | |
227 // | |
228 // Here's description of 'continue from breakpoint' algorithm: | |
229 // a) recover original code at breakpoint address | |
230 // b) enable single step mode of CPU | |
231 // c) call DebugAPI::ContinueDebugEvent | |
232 // d) receive SingleStep exception | |
233 // e) write breakpoint code at breakpoint address | |
234 // f) disable single step mode of CPU | |
235 // h) call DebugAPI::ContinueDebugEvent | |
236 // f) DebuggeeThread is now in 'kRunning' state. | |
237 // Note: we can get different exception on step (d), | |
238 // we have to do the following: | |
239 // 1) disable single step mode of CPU (it's done in first statement in | |
240 // |DebuggeeThread::OnDebugEvent| method). | |
241 // 2) write breakpoint code at breakpoint address, if IP points | |
242 // somewhere else. | |
243 if (!debug_event->IsSingleStep() && | |
244 (kContinueFromBreakpoint == state_) && | |
245 (NULL != triggered_breakpoint_addr_)) { | |
246 void* flat_ip = GetIP(); | |
247 if (IsNaClAppThread()) | |
248 flat_ip = parent_process().FromNexeToFlatAddress(flat_ip); | |
249 | |
250 if (flat_ip != triggered_breakpoint_addr_) { | |
251 Breakpoint* br = | |
252 parent_process().GetBreakpoint(triggered_breakpoint_addr_); | |
253 if (NULL != br) | |
254 br->WriteBreakpointCode(); | |
255 triggered_breakpoint_addr_ = NULL; | |
256 } | |
257 } | |
258 | |
259 // Now we can proceed with debug event. | |
260 switch (de.dwDebugEventCode) { | |
261 case OUTPUT_DEBUG_STRING_EVENT: { | |
262 OnOutputDebugString(debug_event); | |
263 break; | |
264 } | |
265 case EXIT_THREAD_DEBUG_EVENT: { | |
266 exit_code_ = de.u.ExitThread.dwExitCode; | |
267 break; | |
268 } | |
269 case EXIT_PROCESS_DEBUG_EVENT: { | |
270 exit_code_ = de.u.ExitProcess.dwExitCode; | |
271 break; | |
272 } | |
273 case EXCEPTION_DEBUG_EVENT: { | |
274 switch (debug_event->GetExceptionCode()) { | |
275 case EXCEPTION_BREAKPOINT: { | |
276 OnBreakpoint(debug_event); | |
277 break; | |
278 } | |
279 case EXCEPTION_SINGLE_STEP: { | |
280 OnSingleStep(debug_event); | |
281 // Thread halts on all other events unconditionally, | |
282 // but here |OnSingleStep| can decide to continue - | |
283 // in case it's due to 'continue from breakpoint'. | |
284 return; | |
285 } | |
286 } | |
287 } | |
288 } | |
289 // DebugeeThread halts on all debug events, with one exception - | |
290 // when it receives SingleStep while continue from breakpoint. | |
291 SetState(kHalted); | |
292 } | |
293 | |
294 void DebuggeeThread::EnableSingleStep(bool enable) { | |
295 DBG_LOG("TR03.06", | |
296 "msg='DebuggeeThread::EnableSingleStep(%s)' thread_id=%d", | |
297 enable ? "true" : "false", | |
298 id()); | |
299 | |
300 if (parent_process().IsWoW()) { | |
301 WOW64_CONTEXT context; | |
302 GetWowContext(&context); | |
303 if (enable) | |
304 context.EFlags |= 1 << 8; | |
305 else | |
306 context.EFlags &= ~(1 << 8); | |
307 SetWowContext(context); | |
308 } else { | |
309 CONTEXT context; | |
310 GetContext(&context); | |
311 if (enable) | |
312 context.EFlags |= 1 << 8; | |
313 else | |
314 context.EFlags &= ~(1 << 8); | |
315 SetContext(context); | |
316 } | |
317 } | |
318 | |
319 const char* DebuggeeThread::GetStateName(State state) { | |
320 switch (state) { | |
321 case kRunning: return "kRunning"; | |
322 case kHalted: return "kHalted"; | |
323 case kContinueFromBreakpoint: return "kContinueFromBreakpoint"; | |
324 case kDead: return "kDead"; | |
325 } | |
326 return "N/A"; | |
327 } | |
328 | |
329 const char* DebuggeeThread::GetContinueOptionName(ContinueOption option) { | |
330 switch (option) { | |
331 case kSingleStep: return "kSingleStep"; | |
332 case kContinue: return "kContinue"; | |
333 case kContinueAndPassException: return "kContinueAndPassException"; | |
334 } | |
335 return "N/A"; | |
336 } | |
337 | |
338 void DebuggeeThread::Kill() { | |
339 if (NULL != handle_) | |
340 debug_api().TerminateThread(handle_, 0); | |
341 } | |
342 | |
343 bool DebuggeeThread::GetContext(CONTEXT* context) { | |
344 if (!parent_process().IsHalted()) | |
345 return false; | |
346 | |
347 context->ContextFlags = CONTEXT_ALL; | |
348 return (debug_api().GetThreadContext(handle_, context) != FALSE); | |
349 } | |
350 | |
351 bool DebuggeeThread::SetContext(const CONTEXT& context) { | |
352 if (!parent_process().IsHalted()) | |
353 return false; | |
354 | |
355 CONTEXT context_copy = context; | |
356 return (debug_api().SetThreadContext(handle_, &context_copy) != FALSE); | |
357 } | |
358 | |
359 bool DebuggeeThread::GetWowContext(WOW64_CONTEXT* context) { | |
360 if (!parent_process().IsHalted()) | |
361 return false; | |
362 | |
363 context->ContextFlags = CONTEXT_ALL; | |
364 return (debug_api().Wow64GetThreadContext(handle_, context) != FALSE); | |
365 } | |
366 | |
367 bool DebuggeeThread::SetWowContext(const WOW64_CONTEXT& context) { | |
368 if (!parent_process().IsHalted()) | |
369 return false; | |
370 | |
371 return (debug_api().Wow64SetThreadContext(handle_, &context) != FALSE); | |
372 } | |
373 | |
374 void* DebuggeeThread::GetIP() { | |
375 if (!parent_process().IsHalted()) | |
376 return false; | |
377 | |
378 if (parent_process().IsWoW()) { | |
379 WOW64_CONTEXT context; | |
380 GetWowContext(&context); | |
381 return reinterpret_cast<void*>(context.Eip); | |
382 } else { | |
383 CONTEXT context; | |
384 GetContext(&context); | |
385 #ifdef _WIN64 | |
386 return reinterpret_cast<void*>(context.Rip); | |
387 #else | |
388 return reinterpret_cast<void*>(context.Eip); | |
389 #endif | |
390 } | |
391 } | |
392 | |
393 bool DebuggeeThread::SetIP(void* ip) { | |
394 if (!parent_process().IsHalted()) | |
395 return false; | |
396 | |
397 if (parent_process().IsWoW()) { | |
398 WOW64_CONTEXT context; | |
399 if (!GetWowContext(&context)) | |
400 return false; | |
401 context.Eip = reinterpret_cast<DWORD>(ip); | |
402 return SetWowContext(context); | |
403 } else { | |
404 CONTEXT ct; | |
405 if (!GetContext(&ct)) | |
406 return false; | |
407 #ifdef _WIN64 | |
408 ct.Rip = reinterpret_cast<DWORD64>(ip); | |
409 #else | |
410 ct.Eip = reinterpret_cast<DWORD>(ip); | |
411 #endif | |
412 return SetContext(ct); | |
413 } | |
414 } | |
415 } // namespace debug | |
416 | |
OLD | NEW |