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 | |
5 #region | |
6 | |
7 using System; | |
8 using System.Collections.Generic; | |
9 using System.Diagnostics; | |
10 using System.Linq; | |
11 using System.Runtime.CompilerServices; | |
12 using System.Threading; | |
13 using System.Xml.Linq; | |
14 using Google.MsAd7.BaseImpl.Interfaces; | |
15 using Google.NaClVsx.ProjectSupport; | |
16 using NaClVsx.DebugHelpers; | |
17 | |
18 #endregion | |
19 | |
20 namespace Google.NaClVsx.DebugSupport { | |
21 public sealed class NaClDebugger : INaClDebugger { | |
22 public NaClDebugger() { | |
23 BaseAddress = 0; | |
24 symbols_ = new NaClSymbolProvider(this); | |
25 } | |
26 | |
27 public event SimpleDebuggerTypes.EventHandler Stopped; | |
28 public event SimpleDebuggerTypes.EventHandler StepFinished; | |
29 public event SimpleDebuggerTypes.EventHandler Continuing; | |
30 public event SimpleDebuggerTypes.MessageHandler Output; | |
31 public event SimpleDebuggerTypes.ModuleLoadHandler ModuleLoaded; | |
32 public event SimpleDebuggerTypes.MessageHandler Opened; | |
33 | |
34 public string Arch { get; private set; } | |
35 | |
36 public string Path { get; private set; } | |
37 | |
38 #region INaClDebugger Members | |
39 | |
40 public ulong BaseAddress { get; private set; } | |
41 | |
42 [MethodImpl(MethodImplOptions.Synchronized)] | |
43 public void Open(string connectionString) { | |
44 sendStopMessages_ = false; | |
45 gdbProxy_.Open(connectionString); | |
46 | |
47 // Can't set these until after Open() returns | |
48 gdbProxy_.SetStopAsync(OnGdbStop); | |
49 gdbProxy_.SetOutputAsync(OnGdbOutput); | |
50 | |
51 var evt = new EventWaitHandle(false, EventResetMode.AutoReset); | |
52 gdbProxy_.GetArch( | |
53 (r, s, d) => { | |
54 if (GdbProxy.ResultCode.DHR_OK == r) { | |
55 ParseArchString(s); | |
56 } | |
57 evt.Set(); | |
58 }); | |
59 if (!evt.WaitOne(kGdbTimeout)) { | |
60 throw new TimeoutException("GDB connection timed out"); | |
61 } | |
62 | |
63 var regs = new RegsX86_64(); | |
64 gdbProxy_.GetRegisters(ref regs); | |
65 BaseAddress = regs.R15; | |
66 | |
67 var fullNexePath = NaClProjectConfig.GetLastNexe(); | |
68 // note -- LoadModuleWithPath uses |BaseAddress| which we set | |
69 // above by reading register |R15|. Works only in x86-64 sandbox. | |
70 LoadModuleWithPath(fullNexePath); | |
71 /** | |
72 * TODO(mmortensen): In the future we may want to | |
73 * query sel_ldr for the nexe name (and full path?) to | |
74 * make sure we are running the nexe we built...esp | |
75 * when we are launching through chrome and using a server | |
76 * to run our app. | |
77 gdb_proxy_.GetPath( | |
78 (r, s, d) => { | |
79 status = r; | |
80 if (status == GdbProxy.ResultCode.DHR_OK && s!="") { | |
81 LoadModuleWithPath(s); | |
82 } | |
83 else if (s == "") { | |
84 // Set the path based on project data, as obtained from | |
85 // NaClProjectconfig.NexeList. | |
86 LoadModuleWithPath(full_nexe_path); | |
87 } | |
88 evt.Set(); | |
89 }); | |
90 if (!evt.WaitOne(gdbTimeout_)) { | |
91 throw new TimeoutException("GDB connection timed out"); | |
92 } | |
93 **/ | |
94 InvokeOpened(SimpleDebuggerTypes.ResultCode.Ok, Path); | |
95 | |
96 sendStopMessages_ = true; | |
97 gdbWorkerThread_ = new System.Threading.Thread(GdbWorkerThreadProc); | |
98 gdbWorkerThread_.Name = "GDB Proxy Background Worker"; | |
99 gdbWorkerThread_.Start(); | |
100 | |
101 // Debuggee is stopped at nexe entry point. | |
102 // Calling |GetLastSig| results in debugger getting notification about | |
103 // debuggee stopped status. | |
104 var lastSig = 0; | |
105 gdbProxy_.GetLastSig(out lastSig); | |
106 } | |
107 | |
108 [MethodImpl(MethodImplOptions.Synchronized)] | |
109 public void Close() { | |
110 gdbTermEvent_.Set(); | |
111 gdbProxy_.Close(); | |
112 gdbWorkerThread_.Join(kGdbPingInterval * 8); | |
113 if (gdbWorkerThread_.IsAlive) { | |
114 gdbWorkerThread_.Abort(); | |
115 } | |
116 gdbWorkerThread_ = null; | |
117 } | |
118 | |
119 #endregion | |
120 | |
121 #region Implementation of ISimpleDebugger | |
122 | |
123 public string Architecture { | |
124 get { return Arch; } | |
125 } | |
126 | |
127 public ISimpleSymbolProvider Symbols { | |
128 get { return symbols_; } | |
129 } | |
130 | |
131 [MethodImpl(MethodImplOptions.Synchronized)] | |
132 public void Break() { | |
133 gdbProxy_.RequestBreak(); | |
134 } | |
135 | |
136 [MethodImpl(MethodImplOptions.Synchronized)] | |
137 public object GetRegisters(uint id) { | |
138 // FIXME -- |id| does NOT appear to be used by this function!! | |
139 // Note: |id| can be probably used to indicate register sets other than 'g
eneral' registers. | |
140 // For example, set of SSE registers. | |
141 var regs = new RegsX86_64(); | |
142 gdbProxy_.GetRegisters(ref regs); | |
143 if (regs.Rip == 0) { | |
144 Debug.WriteLine("ERROR: regs.RIPS is 0"); | |
145 } else { | |
146 Debug.WriteLine("regs.RIPS is " + String.Format("{0,4:X}", regs.Rip)); | |
147 } | |
148 Debug.WriteLine( | |
149 " GetRegisters.... Rip=" + | |
150 String.Format("{0,4:X}", regs.Rip) + | |
151 " Rsp=" + String.Format("{0,4:X}", regs.Rsp) + | |
152 " SegCS=" + String.Format("{0,4:X}", regs.SegCs) + | |
153 " SegDS=" + String.Format("{0,4:X}", regs.SegDs) + | |
154 " EFlags=" + String.Format("{0,4:X}", regs.EFlags)); | |
155 return regs; | |
156 } | |
157 | |
158 [MethodImpl(MethodImplOptions.Synchronized)] | |
159 public void Step(uint id) { | |
160 // SingleStep until | |
161 // - rip no longer points to the same source line. | |
162 // - some other thread throws a signal. | |
163 // - some signal other than STEP is thrown on this thread | |
164 // | |
165 // Some debugers might implement this a just a single step however, | |
166 // given the number of "NOP" required by the jump alignment, this | |
167 // could be painful, so instead we look for a different line. | |
168 var rip = ((RegsX86_64) GetRegisters(id)).Rip; | |
169 var pos = symbols_.PositionFromAddress(rip); | |
170 | |
171 // Check if we are starting on a breakpoint. If so we need to | |
172 // temporarily remove it or we will immediately trigger a break | |
173 // without moving. | |
174 var bp = gdbProxy_.HasBreakpoint(rip); | |
175 if (StepFinished != null) { | |
176 StepFinished( | |
177 this, | |
178 SimpleDebuggerTypes.EventType.Step, | |
179 SimpleDebuggerTypes.ResultCode.Ok); | |
180 } | |
181 | |
182 do { | |
183 // If we are on a breakpoint, temporarily remove it by | |
184 // stepping over it | |
185 if (bp) { | |
186 RemoveBreakpoint(rip); | |
187 sendStopMessages_ = false; | |
188 } | |
189 gdbProxy_.RequestStep(); | |
190 if (bp) { | |
191 AddBreakpoint(rip); | |
192 sendStopMessages_ = true; | |
193 | |
194 // We only need to check the first step, other BPs are valid | |
195 bp = false; | |
196 } | |
197 | |
198 // If the signal is not a break trap, or if the thead changed | |
199 // something else triggered the stop, so we are done. | |
200 int sig; | |
201 gdbProxy_.GetLastSig(out sig); | |
202 if (sig != kTrapSignal) | |
203 break; | |
204 | |
205 //TODO(noelallen) Add check for thread change... | |
206 //if (id != ) break; | |
207 | |
208 rip = ((RegsX86_64) GetRegisters(id)).Rip; | |
209 } while (pos == symbols_.PositionFromAddress(rip)); | |
210 } | |
211 | |
212 | |
213 [MethodImpl(MethodImplOptions.Synchronized)] | |
214 public void Continue() { | |
215 //TODO(noelallen) - use correct ID below | |
216 var regs = (RegsX86_64)GetRegisters(0); | |
217 var rip = regs.Rip; | |
218 | |
219 Debug.WriteLine("CONTINUE, rip=0x" + String.Format("{0,4:X}", rip)); | |
220 if (gdbProxy_.HasBreakpoint(rip)) { | |
221 Debug.WriteLine( | |
222 "NaClDebugger.cs, Continue()" + | |
223 "-HasBreakpoint = true, rip=" + | |
224 String.Format("{0,4:X}", rip)); | |
225 RemoveBreakpoint(rip); | |
226 // First step one instruction, to prevent a race condition | |
227 // where the IP gets back to the current line before we have | |
228 // a chance to re-enable the breakpoint | |
229 sendStopMessages_ = false; | |
230 gdbProxy_.RequestStep(); | |
231 sendStopMessages_ = true; | |
232 AddBreakpoint(rip); | |
233 } else { | |
234 Debug.WriteLine("NaClDebugger.cs, Continue()-HasBreakpoint = false"); | |
235 } | |
236 | |
237 var result = gdbProxy_.RequestContinue(); | |
238 OnGdbContinue(result); | |
239 } | |
240 | |
241 [MethodImpl(MethodImplOptions.Synchronized)] | |
242 public bool HasBreakpoint(ulong addr) { | |
243 return gdbProxy_.HasBreakpoint(addr); | |
244 } | |
245 | |
246 [MethodImpl(MethodImplOptions.Synchronized)] | |
247 public void AddBreakpoint(ulong addr) { | |
248 gdbProxy_.AddBreakpoint(addr); | |
249 } | |
250 | |
251 [MethodImpl(MethodImplOptions.Synchronized)] | |
252 public void RemoveBreakpoint(ulong addr) { | |
253 gdbProxy_.RemoveBreakpoint(addr); | |
254 } | |
255 | |
256 [MethodImpl(MethodImplOptions.Synchronized)] | |
257 public void AddTempBreakpoint(ulong addr) { | |
258 // Don't if a real one exists | |
259 if (HasBreakpoint(addr)) { | |
260 return; | |
261 } | |
262 gdbProxy_.AddBreakpoint(addr); | |
263 tempBreakpoints_.Add(addr); | |
264 } | |
265 | |
266 [MethodImpl(MethodImplOptions.Synchronized)] | |
267 public void RemoveTempBreakpoints() { | |
268 foreach (var addr in tempBreakpoints_) { | |
269 gdbProxy_.RemoveBreakpoint(addr); | |
270 } | |
271 tempBreakpoints_.Clear(); | |
272 } | |
273 | |
274 [MethodImpl(MethodImplOptions.Synchronized)] | |
275 public IEnumerable<uint> GetThreads() { | |
276 var evt = new EventWaitHandle(false, EventResetMode.AutoReset); | |
277 var tids = new List<uint>(); | |
278 gdbProxy_.GetThreads( | |
279 (r, s, d) => { | |
280 if (GdbProxy.ResultCode.DHR_OK == r) { | |
281 ParseThreadsString(s, tids); | |
282 } | |
283 evt.Set(); | |
284 }); | |
285 evt.WaitOne(); | |
286 return tids; | |
287 } | |
288 | |
289 [MethodImpl(MethodImplOptions.Synchronized)] | |
290 public void GetMemory(ulong sourceAddress, | |
291 Array destination, | |
292 uint countInBytes) { | |
293 var result = gdbProxy_.GetMemory(sourceAddress, destination, countInBytes)
; | |
294 if (result != GdbProxy.ResultCode.DHR_OK) { | |
295 throw new ApplicationException("Failed GetMemory query"); | |
296 } | |
297 } | |
298 | |
299 [MethodImpl(MethodImplOptions.Synchronized)] | |
300 public void SetMemory(ulong destAddress, Array src, uint count) { | |
301 var result = gdbProxy_.SetMemory(destAddress, src, count); | |
302 if (result != GdbProxy.ResultCode.DHR_OK) { | |
303 throw new ApplicationException("Failed SetMemory query"); | |
304 } | |
305 } | |
306 | |
307 public ulong GetU64(ulong address) { | |
308 var data = new byte[8]; | |
309 GetMemory(address, data, 8); | |
310 return BitConverter.ToUInt64(data, 0); | |
311 } | |
312 | |
313 public uint GetU32(ulong address) { | |
314 var data = new byte[4]; | |
315 GetMemory(address, data, 4); | |
316 return BitConverter.ToUInt32(data, 0); | |
317 } | |
318 | |
319 #endregion | |
320 | |
321 #region Private Implementation | |
322 | |
323 private readonly EventWaitHandle gdbTermEvent_ = | |
324 new EventWaitHandle(false, EventResetMode.ManualReset); | |
325 | |
326 private readonly GdbProxy gdbProxy_ = new GdbProxy(); | |
327 private readonly NaClSymbolProvider symbols_; | |
328 private readonly List<ulong> tempBreakpoints_ = new List<ulong>(); | |
329 | |
330 private const int kGdbPingInterval = 1000; // in ms | |
331 private const int kGdbTimeout = 10000; // in ms | |
332 private const int kTrapSignal = 5; | |
333 private System.Threading.Thread gdbWorkerThread_; | |
334 private bool sendStopMessages_ = true; | |
335 | |
336 #endregion | |
337 | |
338 #region Private Implementation | |
339 | |
340 private void GdbWorkerThreadProc() { | |
341 do { | |
342 lock (this) { | |
343 // gdb_proxy_::RequestContinue is not blocking anymore, so this worker
thread | |
344 // has to poll RSP connection for reply (sent by debug server when deb
uggee | |
345 // stops for some reason). | |
346 // gdb_proxy_.WaitForReply will notify callback registered by gdb_prox
y_.SetStopAsync. | |
347 if (gdbProxy_.IsRunning()) | |
348 gdbProxy_.WaitForReply(); | |
349 } | |
350 } while (gdbTermEvent_.WaitOne(kGdbPingInterval * 1) == false); | |
351 } | |
352 | |
353 private void InvokeOpened(SimpleDebuggerTypes.ResultCode status, string msg)
{ | |
354 var handler = Opened; | |
355 if (handler != null) { | |
356 handler(this, status, msg); | |
357 } | |
358 } | |
359 | |
360 private void LoadModuleWithPath(string fullPathToNexe) { | |
361 string status; | |
362 Path = fullPathToNexe; | |
363 Debug.WriteLine("LoadModuleWithPath {" + Path + "}"); | |
364 symbols_.LoadModule(Path, BaseAddress, out status); | |
365 if (ModuleLoaded != null) { | |
366 ModuleLoaded(this, Path, status); | |
367 } | |
368 } | |
369 | |
370 private void OnGdbContinue(GdbProxy.ResultCode result) { | |
371 if (Continuing != null) { | |
372 Continuing( | |
373 this, | |
374 SimpleDebuggerTypes.EventType.Continue, | |
375 (SimpleDebuggerTypes.ResultCode) result); | |
376 } | |
377 } | |
378 | |
379 private void OnGdbOutput(GdbProxy.ResultCode result, string msg, byte[] data
) { | |
380 if (Output != null) { | |
381 Output(this, (SimpleDebuggerTypes.ResultCode) result, msg); | |
382 } | |
383 } | |
384 | |
385 private void OnGdbStop(GdbProxy.ResultCode result, string msg, byte[] data)
{ | |
386 RemoveTempBreakpoints(); | |
387 | |
388 if (sendStopMessages_ && Stopped != null) { | |
389 Debug.WriteLine("Sending stopped message"); | |
390 Stopped( | |
391 this, | |
392 SimpleDebuggerTypes.EventType.Break, | |
393 (SimpleDebuggerTypes.ResultCode) result); | |
394 } | |
395 } | |
396 | |
397 private void ParseArchString(string msg) { | |
398 Debug.WriteLine(msg); | |
399 try { | |
400 var targetString = XElement.Parse(msg); | |
401 var archElements = | |
402 targetString.Descendants("architecture"); | |
403 var el = archElements.FirstOrDefault(); | |
404 Arch = el.Value; | |
405 } | |
406 catch (Exception e) { | |
407 Debug.WriteLine(e.Message); | |
408 } | |
409 } | |
410 | |
411 private void ParseThreadsString(string msg, List<uint> tids) { | |
412 Debug.WriteLine(msg); | |
413 try { | |
414 var threadsString = XElement.Parse(msg); | |
415 foreach (var el in threadsString.Descendants("thread")) { | |
416 var tidStr = el.Attribute("id").Value; | |
417 var tid = Convert.ToUInt32(tidStr, 16); | |
418 | |
419 tids.Add(tid); | |
420 } | |
421 } | |
422 catch (Exception e) { | |
423 Debug.WriteLine(e.Message); | |
424 } | |
425 } | |
426 | |
427 #endregion | |
428 } | |
429 } | |
OLD | NEW |