|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2005 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 # Debuggee.rb is based in parts on debug.rb from Ruby and debuggee.rb. |
|
7 # Original copyrights of these files follow below. |
|
8 # |
|
9 # debug.rb |
|
10 # Copyright (C) 2000 Network Applied Communication Laboratory, Inc. |
|
11 # Copyright (C) 2000 Information-technology Promotion Agency, Japan |
|
12 # |
|
13 # debuggee.rb |
|
14 # Copyright (c) 2000 NAKAMURA, Hiroshi |
|
15 |
|
16 =begin edoc |
|
17 File implementing the real debugger, which is connected to the IDE frontend. |
|
18 =end |
|
19 |
|
20 require 'DebugQuit' |
|
21 require 'rbconfig' |
|
22 |
|
23 class DEBUGGER__ |
|
24 =begin edoc |
|
25 Class implementing the real debugger. |
|
26 =end |
|
27 class Mutex |
|
28 =begin edoc |
|
29 Class implementing a mutex. |
|
30 =end |
|
31 def initialize |
|
32 =begin edoc |
|
33 Constructor |
|
34 =end |
|
35 @locker = nil |
|
36 @waiting = [] |
|
37 @locked = false; |
|
38 end |
|
39 |
|
40 def locked? |
|
41 =begin edoc |
|
42 Method returning the locked state. |
|
43 |
|
44 @return flag indicating the locked state (boolean) |
|
45 =end |
|
46 @locked |
|
47 end |
|
48 |
|
49 def lock |
|
50 =begin edoc |
|
51 Method to lock the mutex. |
|
52 |
|
53 @return the mutex |
|
54 =end |
|
55 return if @locker == Thread.current |
|
56 while (Thread.critical = true; @locked) |
|
57 @waiting.push Thread.current |
|
58 Thread.stop |
|
59 end |
|
60 @locked = true |
|
61 @locker = Thread.current |
|
62 Thread.critical = false |
|
63 self |
|
64 end |
|
65 |
|
66 def unlock |
|
67 =begin edoc |
|
68 Method to unlock the mutex. |
|
69 |
|
70 @return the mutex |
|
71 =end |
|
72 return unless @locked |
|
73 unless @locker == Thread.current |
|
74 raise RuntimeError, "unlocked by other" |
|
75 end |
|
76 Thread.critical = true |
|
77 t = @waiting.shift |
|
78 @locked = false |
|
79 @locker = nil |
|
80 Thread.critical = false |
|
81 t.run if t |
|
82 self |
|
83 end |
|
84 end |
|
85 MUTEX = Mutex.new |
|
86 |
|
87 class Context |
|
88 =begin edoc |
|
89 Class defining the current execution context. |
|
90 =end |
|
91 def initialize |
|
92 =begin edoc |
|
93 Constructor |
|
94 =end |
|
95 if Thread.current == Thread.main |
|
96 @stop_next = 1 |
|
97 else |
|
98 @stop_next = 0 |
|
99 end |
|
100 @last_file = nil |
|
101 @file = nil |
|
102 @line = nil |
|
103 @no_step = nil |
|
104 @frames = [] |
|
105 @frame_pos = 0 #LJ - for FR |
|
106 @finish_pos = 0 |
|
107 @trace = false |
|
108 @catch = ["StandardError"] #LJ - for FR |
|
109 @suspend_next = false |
|
110 end |
|
111 |
|
112 def stop_next(n=1) |
|
113 =begin edoc |
|
114 Method to set the next stop point (i.e. stop at next line). |
|
115 |
|
116 @param counter defining the stop point (int) |
|
117 =end |
|
118 @stop_next = n |
|
119 end |
|
120 |
|
121 def step_over(n=1) |
|
122 =begin edoc |
|
123 Method to set the next stop point skipping function calls. |
|
124 |
|
125 @param counter defining the stop point (int) |
|
126 =end |
|
127 @stop_next = n |
|
128 @no_step = @frames.size - @frame_pos |
|
129 end |
|
130 |
|
131 def step_out |
|
132 =begin edoc |
|
133 Method to set the next stop point after the function call returns. |
|
134 =end |
|
135 if @frame_pos != @frames.size |
|
136 @finish_pos = @frames.size - @frame_pos |
|
137 @frame_pos = 0 |
|
138 @stop_next -= 1 |
|
139 end |
|
140 end |
|
141 |
|
142 def step_continue |
|
143 =begin edoc |
|
144 Method to continue execution until next breakpoint or watch expression. |
|
145 =end |
|
146 @stop_next = 1 |
|
147 @no_step = -1 |
|
148 end |
|
149 |
|
150 def step_quit |
|
151 =begin edoc |
|
152 Method to stop debugging. |
|
153 =end |
|
154 raise DebugQuit.new |
|
155 end |
|
156 |
|
157 def set_suspend |
|
158 =begin edoc |
|
159 Method to suspend all threads. |
|
160 =end |
|
161 @suspend_next = true |
|
162 end |
|
163 |
|
164 def clear_suspend |
|
165 =begin edoc |
|
166 Method to clear the suspend state. |
|
167 =end |
|
168 @suspend_next = false |
|
169 end |
|
170 |
|
171 def suspend_all |
|
172 =begin edoc |
|
173 Method to suspend all threads. |
|
174 =end |
|
175 DEBUGGER__.suspend |
|
176 end |
|
177 |
|
178 def resume_all |
|
179 =begin edoc |
|
180 Method to resume all threads. |
|
181 =end |
|
182 DEBUGGER__.resume |
|
183 end |
|
184 |
|
185 def check_suspend |
|
186 =begin edoc |
|
187 Method to check the suspend state. |
|
188 =end |
|
189 while (Thread.critical = true; @suspend_next) |
|
190 DEBUGGER__.waiting.push Thread.current |
|
191 @suspend_next = false |
|
192 Thread.stop |
|
193 end |
|
194 Thread.critical = false |
|
195 end |
|
196 |
|
197 def stdout |
|
198 =begin edoc |
|
199 Method returning the stdout object. |
|
200 |
|
201 @return reference to the stdout object |
|
202 =end |
|
203 DEBUGGER__.stdout |
|
204 end |
|
205 |
|
206 def break_points |
|
207 =begin edoc |
|
208 Method to return the list of breakpoints |
|
209 |
|
210 @return Array containing all breakpoints. |
|
211 =end |
|
212 DEBUGGER__.break_points |
|
213 end |
|
214 |
|
215 def context(th) |
|
216 =begin edoc |
|
217 Method returning the context of a thread. |
|
218 |
|
219 @param th thread object to get the context for |
|
220 @return the context for the thread |
|
221 =end |
|
222 DEBUGGER__.context(th) |
|
223 end |
|
224 |
|
225 def attached? |
|
226 =begin edoc |
|
227 Method returning the attached state. |
|
228 |
|
229 @return flag indicating, whether the debugger is attached to the IDE. |
|
230 =end |
|
231 DEBUGGER__.attached? |
|
232 end |
|
233 |
|
234 def set_last_thread(th) |
|
235 =begin edoc |
|
236 Method to remember the last thread. |
|
237 |
|
238 @param th thread to be remembered. |
|
239 =end |
|
240 DEBUGGER__.set_last_thread(th) |
|
241 end |
|
242 |
|
243 def debug_silent_eval(str, binding_) |
|
244 =begin edoc |
|
245 Method to eval a string without output. |
|
246 |
|
247 @param str String containing the expression to be evaluated |
|
248 @param binding_ the binding for the evaluation |
|
249 @return the result of the evaluation |
|
250 =end |
|
251 val = eval(str, binding_) |
|
252 val |
|
253 end |
|
254 |
|
255 def thnum |
|
256 =begin edoc |
|
257 Method returning the thread number of the current thread. |
|
258 |
|
259 @return thread number of the current thread. |
|
260 =end |
|
261 num = DEBUGGER__.instance_eval{@thread_list[Thread.current]} |
|
262 unless num |
|
263 DEBUGGER__.make_thread_list |
|
264 num = DEBUGGER__.instance_eval{@thread_list[Thread.current]} |
|
265 end |
|
266 num |
|
267 end |
|
268 |
|
269 def debug_command(file, line, id, binding_) |
|
270 =begin edoc |
|
271 Method to execute the next debug command. |
|
272 =end |
|
273 MUTEX.lock |
|
274 set_last_thread(Thread.current) |
|
275 unless attached? |
|
276 MUTEX.unlock |
|
277 resume_all |
|
278 return |
|
279 end |
|
280 @frame_pos = 0 |
|
281 @frames[0] = [binding_, file, line, id] |
|
282 stdout.printf_line(@frames) |
|
283 MUTEX.unlock |
|
284 resume_all |
|
285 eventLoop |
|
286 end |
|
287 |
|
288 def frame_set_pos(file, line) |
|
289 =begin edoc |
|
290 Method to set the frame position of the current frame. |
|
291 =end |
|
292 if @frames[0] |
|
293 @frames[0][1] = file |
|
294 @frames[0][2] = line |
|
295 end |
|
296 end |
|
297 |
|
298 def check_break_points(file, pos, binding_, id) |
|
299 =begin edoc |
|
300 Method to check, if the given position contains an active breakpoint. |
|
301 |
|
302 @param file filename containing the currently executed line (String) |
|
303 @param pos line number currently executed (int) |
|
304 @param binding_ current binding object |
|
305 @param id (ignored) |
|
306 @return flag indicating an active breakpoint (boolean) |
|
307 =end |
|
308 # bp[0] enabled flag |
|
309 # bp[1] 0 = breakpoint, 1 = watch expression |
|
310 # bp[2] filename |
|
311 # bp[3] linenumber |
|
312 # bp[4] temporary flag |
|
313 # bp[5] condition |
|
314 # bp[6] ignore count |
|
315 # bp[7] special condition |
|
316 # bp[8] hash of special values |
|
317 return false if break_points.empty? |
|
318 for b in break_points |
|
319 if b[0] |
|
320 if b[1] == 0 and b[2] == file and b[3] == pos # breakpoint |
|
321 # Evaluate condition |
|
322 if b[5] |
|
323 begin |
|
324 if debug_silent_eval(b[5], binding_) |
|
325 if b[6] == 0 # ignore count reached |
|
326 # Delete once reached if temporary breakpoint |
|
327 clear_break_point(file, pos) if b[4] |
|
328 return true |
|
329 else |
|
330 b[6] -= 1 |
|
331 end |
|
332 end |
|
333 rescue StandardError, ScriptError |
|
334 nil |
|
335 end |
|
336 else |
|
337 if b[6] == 0 # ignore count reached |
|
338 # Delete once reached if temporary breakpoint |
|
339 clear_break_point(file, pos) if b[4] |
|
340 return true |
|
341 else |
|
342 b[6] -= 1 |
|
343 end |
|
344 end |
|
345 elsif b[1] == 1 # watch expression |
|
346 begin |
|
347 bd = @frame_pos |
|
348 val = debug_silent_eval(b[5], binding_) |
|
349 if b[7].length() > 0 |
|
350 if b[7] == "??created??" |
|
351 if b[8][bd][0] == false |
|
352 b[8][bd][0] = true |
|
353 b[8][bd][1] = val |
|
354 return true |
|
355 else |
|
356 next |
|
357 end |
|
358 end |
|
359 b[8][bd][0] = true |
|
360 if b[7] == "??changed??" |
|
361 if b[8][bd][1] != val |
|
362 b[8][bd][1] = val |
|
363 if b[8][bd][2] > 0 |
|
364 b[8][bd][2] -= 1 |
|
365 next |
|
366 else |
|
367 return true |
|
368 end |
|
369 else |
|
370 next |
|
371 end |
|
372 end |
|
373 next |
|
374 end |
|
375 if val |
|
376 if b[6] == 0 # ignore count reached |
|
377 # Delete once reached if temporary breakpoint |
|
378 clear_watch_point(b[2]) if b[4] |
|
379 return true |
|
380 else |
|
381 b[6] -= 1 |
|
382 end |
|
383 end |
|
384 rescue StandardError, ScriptError |
|
385 if b[7].length() > 0 |
|
386 if b[8][bd] |
|
387 b[8][bd][0] = false |
|
388 else |
|
389 b[8][bd] = [false, nil, b[6]] |
|
390 end |
|
391 else |
|
392 val = nil |
|
393 end |
|
394 end |
|
395 end |
|
396 end |
|
397 end |
|
398 return false |
|
399 end |
|
400 |
|
401 def clear_break_point(file, pos) |
|
402 =begin edoc |
|
403 Method to delete a specific breakpoint. |
|
404 |
|
405 @param file filename containing the breakpoint (String) |
|
406 @param pos line number containing the breakpoint (int) |
|
407 =end |
|
408 delete_break_point(file, pos) |
|
409 stdout.printf_clear_breakpoint(file, pos) |
|
410 end |
|
411 |
|
412 def add_break_point(file, pos, temp = false, cond = nil) |
|
413 =begin edoc |
|
414 Method to add a breakpoint. |
|
415 |
|
416 @param file filename for the breakpoint (String) |
|
417 @param pos line number for the breakpoint (int) |
|
418 @param temp flag indicating a temporary breakpoint (boolean) |
|
419 @param cond condition of a conditional breakpoint (String) |
|
420 =end |
|
421 break_points.push [true, 0, file, pos, temp, cond, 0] |
|
422 end |
|
423 |
|
424 def delete_break_point(file, pos) |
|
425 =begin edoc |
|
426 Method to delete a breakpoint. |
|
427 |
|
428 @param file filename of the breakpoint (String) |
|
429 @param pos line number of the breakpoint (int) |
|
430 =end |
|
431 break_points.delete_if { |bp| |
|
432 bp[1] == 0 and bp[2] == file and bp[3] == pos |
|
433 } |
|
434 end |
|
435 |
|
436 def enable_break_point(file, pos, enable) |
|
437 =begin edoc |
|
438 Method to set the enabled state of a breakpoint. |
|
439 |
|
440 @param file filename of the breakpoint (String) |
|
441 @param pos line number of the breakpoint (int) |
|
442 @param enable flag indicating the new enabled state (boolean) |
|
443 =end |
|
444 for bp in break_points |
|
445 if (bp[1] == 0 and bp[2] == file and bp[3] == pos) |
|
446 bp[0] = enable |
|
447 break |
|
448 end |
|
449 end |
|
450 end |
|
451 |
|
452 def ignore_break_point(file, pos, count) |
|
453 =begin edoc |
|
454 Method to set the ignore count of a breakpoint. |
|
455 |
|
456 @param file filename of the breakpoint (String) |
|
457 @param pos line number of the breakpoint (int) |
|
458 @param count ignore count to be set (int) |
|
459 =end |
|
460 for bp in break_points |
|
461 if (bp[2] == file and bp[3] == pos) |
|
462 bp[6] = count |
|
463 break |
|
464 end |
|
465 end |
|
466 end |
|
467 |
|
468 def clear_watch_point(cond) |
|
469 =begin edoc |
|
470 Method to delete a specific watch expression. |
|
471 |
|
472 @param cond expression specifying the watch expression (String) |
|
473 =end |
|
474 delete_watch_point(cond) |
|
475 stdout.printf_clear_watchexpression(cond) |
|
476 end |
|
477 |
|
478 def add_watch_point(cond, temp = false) |
|
479 =begin edoc |
|
480 Method to add a watch expression. |
|
481 |
|
482 @param cond expression of the watch expression (String) |
|
483 @param temp flag indicating a temporary watch expression (boolean) |
|
484 =end |
|
485 co1, co2 = cond.split() |
|
486 if co2 == "??created??" or co2 == "??changed??" |
|
487 break_points.push [true, 1, cond, 0, temp, co1, 0, co2, {}] |
|
488 else |
|
489 break_points.push [true, 1, cond, 0, temp, cond, 0, "", {}] |
|
490 end |
|
491 end |
|
492 |
|
493 def delete_watch_point(cond) |
|
494 =begin edoc |
|
495 Method to delete a watch expression. |
|
496 |
|
497 @param cond expression of the watch expression (String) |
|
498 =end |
|
499 break_points.delete_if { |bp| |
|
500 bp[1] == 1 and bp[2] == cond |
|
501 } |
|
502 end |
|
503 |
|
504 def enable_watch_point(cond, enable) |
|
505 =begin edoc |
|
506 Method to set the enabled state of a watch expression. |
|
507 |
|
508 @param cond expression of the watch expression (String) |
|
509 @param enable flag indicating the new enabled state (boolean) |
|
510 =end |
|
511 for bp in break_points |
|
512 if (bp[1] == 1 and bp[2] == cond) |
|
513 bp[0] = enable |
|
514 break |
|
515 end |
|
516 end |
|
517 end |
|
518 |
|
519 def ignore_watch_point(cond, count) |
|
520 =begin edoc |
|
521 Method to set the ignore count of a watch expression. |
|
522 |
|
523 @param cond expression of the watch expression (String) |
|
524 @param count ignore count to be set (int) |
|
525 =end |
|
526 for bp in break_points |
|
527 if (bp[1] == 1 and bp[2] == cond) |
|
528 bp[6] = count |
|
529 break |
|
530 end |
|
531 end |
|
532 end |
|
533 |
|
534 def excn_handle(file, line, id, binding_) |
|
535 =begin edoc |
|
536 Method to handle an exception |
|
537 |
|
538 @param file filename containing the currently executed line (String) |
|
539 @param pos line number currently executed (int) |
|
540 @param id (ignored) |
|
541 @param binding_ current binding object |
|
542 =end |
|
543 if $!.class <= SystemExit |
|
544 set_trace_func nil |
|
545 stdout.printf_exit($!.status) |
|
546 return |
|
547 elsif $!.class <= ScriptError |
|
548 msgParts = $!.message.split(":", 3) |
|
549 filename = File.expand_path(msgParts[0]) |
|
550 linenr = msgParts[1].to_i |
|
551 exclist = ["", [filename, linenr, 0]] |
|
552 stdout.printf_scriptExcn(exclist) |
|
553 else |
|
554 exclist = ["%s" % $!.class, "%s" % $!, [file, line]] |
|
555 @frames.each do |_binding, _file, _line, _id| |
|
556 next if [_file, _line] == exclist[-1] |
|
557 exclist << [_file, _line] |
|
558 end |
|
559 stdout.printf_excn(exclist) |
|
560 end |
|
561 debug_command(file, line, id, binding_) |
|
562 end |
|
563 |
|
564 def skip_it?(file) |
|
565 =begin edoc |
|
566 Method to filter out debugger files. |
|
567 |
|
568 Tracing is turned off for files that are part of the |
|
569 debugger that are called from the application being debugged. |
|
570 |
|
571 @param file name of the file to be checked (String) |
|
572 @return flag indicating, whether the file should be skipped (boolean) |
|
573 =end |
|
574 if file =~ /\(eval\)/ |
|
575 return true |
|
576 end |
|
577 |
|
578 if not traceRuby? and |
|
579 (file =~ /#{Config::CONFIG['sitelibdir']}/ or |
|
580 file =~ /#{Config::CONFIG['rubylibdir']}/) |
|
581 return true |
|
582 end |
|
583 |
|
584 if ["AsyncFile.rb", "AsyncIO.rb", "Config.rb", "DebugClient.rb", |
|
585 "DebugClientBaseModule.rb", "DebugClientCapabilities.rb", |
|
586 "DebugProtocol.rb", "DebugQuit.rb", "Debuggee.rb"].include?(\ |
|
587 File.basename(file)) |
|
588 return true |
|
589 end |
|
590 return false |
|
591 end |
|
592 |
|
593 def trace_func(event, file, line, id, binding_, klass) |
|
594 =begin edoc |
|
595 Method executed by the tracing facility. |
|
596 |
|
597 @param event the tracing event (String) |
|
598 @param file the name of the file being traced (String) |
|
599 @param line the line number being traced (int) |
|
600 @param id object id |
|
601 @param binding_ a binding object |
|
602 @param klass name of a class |
|
603 =end |
|
604 context(Thread.current).check_suspend |
|
605 |
|
606 if skip_it?(file) and not ["call","return"].include?(event) |
|
607 case event |
|
608 when 'line' |
|
609 frame_set_pos(file, line) |
|
610 |
|
611 when 'call' |
|
612 @frames.unshift [binding_, file, line, id] |
|
613 |
|
614 when 'c-call' |
|
615 frame_set_pos(file, line) |
|
616 |
|
617 when 'class' |
|
618 @frames.unshift [binding_, file, line, id] |
|
619 |
|
620 when 'return', 'end' |
|
621 @frames.shift |
|
622 |
|
623 when 'end' |
|
624 @frames.shift |
|
625 |
|
626 when 'raise' |
|
627 excn_handle(file, line, id, binding_) |
|
628 |
|
629 end |
|
630 @last_file = file |
|
631 return |
|
632 end |
|
633 |
|
634 @file = file |
|
635 @line = line |
|
636 |
|
637 case event |
|
638 when 'line' |
|
639 frame_set_pos(file, line) |
|
640 eventPoll |
|
641 if !@no_step or @frames.size == @no_step |
|
642 @stop_next -= 1 |
|
643 @stop_next = -1 if @stop_next < 0 |
|
644 elsif @frames.size < @no_step |
|
645 @stop_next = 0 # break here before leaving... |
|
646 else |
|
647 # nothing to do. skipped. |
|
648 end |
|
649 if check_break_points(file, line, binding_, id) or @stop_next == 0 |
|
650 @no_step = nil |
|
651 suspend_all |
|
652 debug_command(file, line, id, binding_) |
|
653 end |
|
654 |
|
655 when 'call' |
|
656 @frames.unshift [binding_, file, line, id] |
|
657 if check_break_points(file, id.id2name, binding_, id) or |
|
658 check_break_points(klass.to_s, id.id2name, binding_, id) |
|
659 suspend_all |
|
660 debug_command(file, line, id, binding_) |
|
661 end |
|
662 |
|
663 when 'c-call' |
|
664 frame_set_pos(file, line) |
|
665 if id == :require and klass == Kernel |
|
666 @frames.unshift [binding_, file, line, id] |
|
667 else |
|
668 frame_set_pos(file, line) |
|
669 end |
|
670 |
|
671 when 'c-return' |
|
672 if id == :require and klass == Kernel |
|
673 if @frames.size == @finish_pos |
|
674 @stop_next = 1 |
|
675 @finish_pos = 0 |
|
676 end |
|
677 @frames.shift |
|
678 end |
|
679 |
|
680 when 'class' |
|
681 @frames.unshift [binding_, file, line, id] |
|
682 |
|
683 when 'return', 'end' |
|
684 if @frames.size == @finish_pos |
|
685 @stop_next = 1 |
|
686 @finish_pos = 0 |
|
687 end |
|
688 @frames.shift |
|
689 |
|
690 when 'end' |
|
691 @frames.shift |
|
692 |
|
693 when 'raise' |
|
694 @no_step = nil |
|
695 @stop_next = 0 # break here before leaving... |
|
696 excn_handle(file, line, id, binding_) |
|
697 |
|
698 end |
|
699 @last_file = file |
|
700 end |
|
701 end |
|
702 |
|
703 trap("INT") { DEBUGGER__.interrupt } |
|
704 @last_thread = Thread::main |
|
705 @max_thread = 1 |
|
706 @thread_list = {Thread::main => 1} |
|
707 @break_points = [] |
|
708 @waiting = [] |
|
709 @stdout = STDOUT |
|
710 @loaded_files = {} |
|
711 |
|
712 class SilentObject |
|
713 =begin edoc |
|
714 Class defining an object that ignores all messages. |
|
715 =end |
|
716 def method_missing( msg_id, *a, &b ) |
|
717 =begin edoc |
|
718 Method invoked for all messages it cannot handle. |
|
719 |
|
720 @param msg_id symbol for the method called |
|
721 @param *a arguments passed to the missing method |
|
722 @param &b unknown |
|
723 =end |
|
724 end |
|
725 end |
|
726 SilentClient = SilentObject.new() |
|
727 @client = SilentClient |
|
728 @attached = false |
|
729 |
|
730 class <<DEBUGGER__ |
|
731 =begin edoc |
|
732 Class defining a singleton object for the debugger. |
|
733 =end |
|
734 def stdout |
|
735 =begin edoc |
|
736 Method returning the stdout object. |
|
737 |
|
738 @return reference to the stdout object |
|
739 =end |
|
740 @stdout |
|
741 end |
|
742 |
|
743 def stdout=(s) |
|
744 =begin edoc |
|
745 Method to set the stdout object. |
|
746 |
|
747 @param s reference to the stdout object |
|
748 =end |
|
749 @stdout = s |
|
750 end |
|
751 |
|
752 def break_points |
|
753 =begin edoc |
|
754 Method to return the list of breakpoints |
|
755 |
|
756 @return Array containing all breakpoints. |
|
757 =end |
|
758 @break_points |
|
759 end |
|
760 |
|
761 def last_thread |
|
762 =begin edoc |
|
763 Method returning the last active thread. |
|
764 |
|
765 @return active thread |
|
766 =end |
|
767 @last_thread |
|
768 end |
|
769 |
|
770 def attach( debugger ) |
|
771 =begin edoc |
|
772 Method to connect the debugger to the IDE. |
|
773 |
|
774 @param debugger reference to the object handling the |
|
775 communication with the IDE. |
|
776 =end |
|
777 unless @attached |
|
778 set_client( debugger ) |
|
779 @attached = true |
|
780 interrupt |
|
781 else |
|
782 false |
|
783 end |
|
784 end |
|
785 |
|
786 def client |
|
787 =begin edoc |
|
788 Method returning a reference to the client object. |
|
789 |
|
790 @return reference to the client object. |
|
791 =end |
|
792 @client |
|
793 end |
|
794 |
|
795 def set_client( debugger ) |
|
796 =begin edoc |
|
797 Method to set the client handling the connection. |
|
798 |
|
799 @param debugger reference to the object handling the connection |
|
800 =end |
|
801 @client = Client.new( debugger ) |
|
802 DEBUGGER__.stdout = @client |
|
803 end |
|
804 |
|
805 def attached? |
|
806 =begin edoc |
|
807 Method returning the attached state. |
|
808 |
|
809 @return flag indicating, whether the debugger is attached to the IDE. |
|
810 =end |
|
811 @attached |
|
812 end |
|
813 |
|
814 def quit(status = 0) |
|
815 =begin edoc |
|
816 Method to quit the debugger. |
|
817 |
|
818 @param status exit status of the program |
|
819 =end |
|
820 @client.printf_exit(status) |
|
821 STDERR.flush; STDOUT.flush |
|
822 end |
|
823 |
|
824 def waiting |
|
825 =begin edoc |
|
826 Method returning the waiting list. |
|
827 |
|
828 @return the waiting list |
|
829 =end |
|
830 @waiting |
|
831 end |
|
832 |
|
833 def set_last_thread(th) |
|
834 =begin edoc |
|
835 Method to remember the last thread. |
|
836 |
|
837 @param th thread to be remembered. |
|
838 =end |
|
839 @last_thread = th |
|
840 end |
|
841 |
|
842 def suspend |
|
843 =begin edoc |
|
844 Method to suspend the program being debugged. |
|
845 =end |
|
846 Thread.critical = true |
|
847 make_thread_list |
|
848 for th, in @thread_list |
|
849 next if th == Thread.current |
|
850 context(th).set_suspend |
|
851 end |
|
852 Thread.critical = false |
|
853 # Schedule other threads to suspend as soon as possible. |
|
854 Thread.pass |
|
855 end |
|
856 |
|
857 def resume |
|
858 =begin edoc |
|
859 Method to resume the program being debugged. |
|
860 =end |
|
861 Thread.critical = true |
|
862 make_thread_list |
|
863 for th, in @thread_list |
|
864 next if th == Thread.current |
|
865 context(th).clear_suspend |
|
866 end |
|
867 waiting.each do |th| |
|
868 th.run |
|
869 end |
|
870 waiting.clear |
|
871 Thread.critical = false |
|
872 # Schedule other threads to restart as soon as possible. |
|
873 Thread.pass |
|
874 end |
|
875 |
|
876 def context(thread=Thread.current) |
|
877 =begin edoc |
|
878 Method returning the context of a thread. |
|
879 |
|
880 @param th threat the context is requested for |
|
881 @return context object for the thread |
|
882 =end |
|
883 c = thread[:__debugger_data__] |
|
884 unless c |
|
885 thread[:__debugger_data__] = c = Context.new |
|
886 end |
|
887 c |
|
888 end |
|
889 |
|
890 def interrupt |
|
891 =begin edoc |
|
892 Method to stop execution at the next instruction. |
|
893 =end |
|
894 context(@last_thread).stop_next |
|
895 end |
|
896 |
|
897 def get_thread(num) |
|
898 =begin edoc |
|
899 Method returning a thread by number. |
|
900 |
|
901 @param num thread number (int) |
|
902 @return thread with the requested number |
|
903 =end |
|
904 th = @thread_list.index(num) |
|
905 unless th |
|
906 @stdout.print "No thread ##{num}\n" |
|
907 throw :debug_error |
|
908 end |
|
909 th |
|
910 end |
|
911 |
|
912 def thread_list(num) |
|
913 =begin edoc |
|
914 Method to list the state of a thread. |
|
915 |
|
916 @param num thread number (int) |
|
917 =end |
|
918 th = get_thread(num) |
|
919 if th == Thread.current |
|
920 @stdout.print "+" |
|
921 else |
|
922 @stdout.print " " |
|
923 end |
|
924 @stdout.printf "%d ", num |
|
925 @stdout.print th.inspect, "\t" |
|
926 file = context(th).instance_eval{@file} |
|
927 if file |
|
928 @stdout.print file,":",context(th).instance_eval{@line} |
|
929 end |
|
930 @stdout.print "\n" |
|
931 end |
|
932 |
|
933 def thread_list_all |
|
934 =begin edoc |
|
935 Method to list the state of all threads. |
|
936 =end |
|
937 for th in @thread_list.values.sort |
|
938 thread_list(th) |
|
939 end |
|
940 end |
|
941 |
|
942 def make_thread_list |
|
943 =begin edoc |
|
944 Method to create a thread list. |
|
945 =end |
|
946 hash = {} |
|
947 for th in Thread::list |
|
948 next if (th[:__debugger_hidden__]) |
|
949 if @thread_list.key? th |
|
950 hash[th] = @thread_list[th] |
|
951 else |
|
952 @max_thread += 1 |
|
953 hash[th] = @max_thread |
|
954 end |
|
955 end |
|
956 @thread_list = hash |
|
957 end |
|
958 |
|
959 def debug_thread_info(input, binding_) |
|
960 =begin edoc |
|
961 Method handling the thread related debug commands. |
|
962 |
|
963 @param input debug command (String) |
|
964 @param binding_ reference to the binding object |
|
965 =end |
|
966 case input |
|
967 when /^l(?:ist)?/ |
|
968 make_thread_list |
|
969 thread_list_all |
|
970 |
|
971 when /^c(?:ur(?:rent)?)?$/ |
|
972 make_thread_list |
|
973 thread_list(@thread_list[Thread.current]) |
|
974 |
|
975 when /^(?:sw(?:itch)?\s+)?(\d+)/ |
|
976 make_thread_list |
|
977 th = get_thread($1.to_i) |
|
978 if th == Thread.current |
|
979 @stdout.print "It's the current thread.\n" |
|
980 else |
|
981 thread_list(@thread_list[th]) |
|
982 context(th).stop_next |
|
983 th.run |
|
984 return :cont |
|
985 end |
|
986 |
|
987 when /^stop\s+(\d+)/ |
|
988 make_thread_list |
|
989 th = get_thread($1.to_i) |
|
990 if th == Thread.current |
|
991 @stdout.print "It's the current thread.\n" |
|
992 elsif th.stop? |
|
993 @stdout.print "Already stopped.\n" |
|
994 else |
|
995 thread_list(@thread_list[th]) |
|
996 context(th).suspend |
|
997 end |
|
998 |
|
999 when /^resume\s+(\d+)/ |
|
1000 make_thread_list |
|
1001 th = get_thread($1.to_i) |
|
1002 if th == Thread.current |
|
1003 @stdout.print "It's the current thread.\n" |
|
1004 elsif !th.stop? |
|
1005 @stdout.print "Already running." |
|
1006 else |
|
1007 thread_list(@thread_list[th]) |
|
1008 th.run |
|
1009 end |
|
1010 end |
|
1011 end |
|
1012 |
|
1013 def eventLoop |
|
1014 =begin edoc |
|
1015 Method calling the main event loop. |
|
1016 =end |
|
1017 @client.eventLoop |
|
1018 end |
|
1019 |
|
1020 def eventPoll |
|
1021 =begin edoc |
|
1022 Method calling the main function polling for an event sent by the IDE. |
|
1023 =end |
|
1024 @client.eventPoll |
|
1025 end |
|
1026 |
|
1027 def traceRuby? |
|
1028 =begin edoc |
|
1029 Method to check, if we should trace into the Ruby interpreter libraries. |
|
1030 =end |
|
1031 @client.traceRuby? |
|
1032 end |
|
1033 end |
|
1034 |
|
1035 |
|
1036 class Context |
|
1037 def eventLoop |
|
1038 =begin edoc |
|
1039 Method calling the main event loop. |
|
1040 =end |
|
1041 DEBUGGER__.eventLoop |
|
1042 end |
|
1043 |
|
1044 def eventPoll |
|
1045 =begin edoc |
|
1046 Method calling the main function polling for an event sent by the IDE. |
|
1047 =end |
|
1048 DEBUGGER__.eventPoll |
|
1049 end |
|
1050 |
|
1051 def traceRuby? |
|
1052 =begin edoc |
|
1053 Method to check, if we should trace into the Ruby interpreter libraries. |
|
1054 =end |
|
1055 DEBUGGER__.traceRuby? |
|
1056 end |
|
1057 end |
|
1058 |
|
1059 require 'DebugProtocol' |
|
1060 |
|
1061 class Client |
|
1062 =begin edoc |
|
1063 Class handling the connection to the IDE. |
|
1064 =end |
|
1065 def initialize( debugger ) |
|
1066 =begin edoc |
|
1067 Constructor |
|
1068 |
|
1069 @param debugger reference to the object having the IDE connection. |
|
1070 =end |
|
1071 @debugger = debugger |
|
1072 end |
|
1073 |
|
1074 def eventLoop |
|
1075 =begin edoc |
|
1076 Method calling the main event loop. |
|
1077 =end |
|
1078 @debugger.eventLoop() |
|
1079 end |
|
1080 |
|
1081 def eventPoll |
|
1082 =begin edoc |
|
1083 Method calling the main function polling for an event sent by the IDE. |
|
1084 =end |
|
1085 @debugger.eventPoll() |
|
1086 end |
|
1087 |
|
1088 def traceRuby? |
|
1089 =begin edoc |
|
1090 Method to check, if we should trace into the Ruby interpreter libraries. |
|
1091 =end |
|
1092 @debugger.traceRuby |
|
1093 end |
|
1094 |
|
1095 def printf( *args ) |
|
1096 =begin edoc |
|
1097 Method to print something to the IDE. |
|
1098 |
|
1099 @param *args Arguments to be printed. |
|
1100 =end |
|
1101 @debugger.write("#{args.join(', ')}\n") |
|
1102 end |
|
1103 |
|
1104 def printf_line(frames) |
|
1105 =begin edoc |
|
1106 Method to report the current line and the current stack trace to the IDE. |
|
1107 |
|
1108 @param frames reference to the array containing the stack trace. |
|
1109 =end |
|
1110 fr_list = [] |
|
1111 for bind, file, line, id in frames |
|
1112 break unless bind |
|
1113 break if file =~ /\(eval\)/ |
|
1114 fr_list << [file, line, id ? id.id2name : ''] |
|
1115 end |
|
1116 |
|
1117 @debugger.write("%s%s\n" % [ResponseLine, fr_list.inspect]) |
|
1118 end |
|
1119 |
|
1120 def printf_excn(exclist) |
|
1121 =begin edoc |
|
1122 Method to report an exception to the IDE. |
|
1123 |
|
1124 @param exclist info about the exception to be reported |
|
1125 =end |
|
1126 @debugger.write("%s%s\n" % [ResponseException, exclist.inspect]) |
|
1127 end |
|
1128 |
|
1129 def printf_scriptExcn(exclist) |
|
1130 =begin edoc |
|
1131 Method to report a ScriptError to the IDE. |
|
1132 |
|
1133 @param exclist info about the exception to be reported |
|
1134 =end |
|
1135 @debugger.write("%s%s\n" % [ResponseSyntax, exclist.inspect]) |
|
1136 end |
|
1137 |
|
1138 def printf_clear_breakpoint(file, line) |
|
1139 =begin edoc |
|
1140 Method to report the deletion of a temporary breakpoint to the IDE. |
|
1141 |
|
1142 @param file filename of the breakpoint (String) |
|
1143 @param line line number of the breakpoint (int) |
|
1144 =end |
|
1145 @debugger.write("%s%s,%d\n" % [ResponseClearBreak, file, line]) |
|
1146 end |
|
1147 |
|
1148 def printf_clear_watchexpression(cond) |
|
1149 =begin edoc |
|
1150 Method to report the deletion of a temporary watch expression to the IDE. |
|
1151 |
|
1152 @param cond expression of the watch expression (String) |
|
1153 =end |
|
1154 @debugger.write("%s%s\n" % [ResponseClearWatch, cond]) |
|
1155 end |
|
1156 |
|
1157 def printf_exit(status) |
|
1158 =begin edoc |
|
1159 Method to report the exit status to the IDE. |
|
1160 |
|
1161 @param status exit status of the program (int) |
|
1162 =end |
|
1163 @debugger.write("%s%d\n" % [ResponseExit, status]) |
|
1164 end |
|
1165 end |
|
1166 |
|
1167 class Context |
|
1168 def current_frame |
|
1169 =begin edoc |
|
1170 Method returning the current execution frame. |
|
1171 |
|
1172 @return current execution frame |
|
1173 =end |
|
1174 @frames[@frame_pos] |
|
1175 end |
|
1176 |
|
1177 def get_frame(frameno) |
|
1178 =begin edoc |
|
1179 Method returning a specific execution frame. |
|
1180 |
|
1181 @param frameno frame number of the frame to be returned (int) |
|
1182 @return the requested execution frame |
|
1183 =end |
|
1184 @frames[frameno] |
|
1185 end |
|
1186 |
|
1187 def current_binding |
|
1188 =begin edoc |
|
1189 Method returning the binding object of the current execution frame. |
|
1190 |
|
1191 @return binding object of the current execution frame |
|
1192 =end |
|
1193 @frames[@frame_pos][0] |
|
1194 end |
|
1195 |
|
1196 def get_binding(frameno) |
|
1197 =begin edoc |
|
1198 Method returning the binding object of a specific execution frame. |
|
1199 |
|
1200 @param frameno frame number of the frame (int) |
|
1201 @return the requested binding object |
|
1202 =end |
|
1203 @frames[frameno][0] |
|
1204 end |
|
1205 end |
|
1206 |
|
1207 Thread.main["name"] = 'Main' |
|
1208 end |