16 |
|
|
17 |
require 'open3' |
require 'open3' |
18 |
require 'drb/drb' |
require 'drb/drb' |
19 |
|
require 'singleton' |
20 |
|
require 'rubyide_tools_debugger/breakpoint' |
21 |
|
|
22 |
module FreeRIDE; module GUI |
module FreeRIDE; module GUI |
23 |
|
|
46 |
session = nil |
session = nil |
47 |
cmd = cmd_mgr.add("App/Run/Debugger", "&Debugger") do |cmd_slot| |
cmd = cmd_mgr.add("App/Run/Debugger", "&Debugger") do |cmd_slot| |
48 |
if debugger |
if debugger |
49 |
if debugger.manager.running? |
if debugger.manager.running? |
50 |
debugger.manager.show |
debugger.manager.show |
51 |
else |
else |
52 |
debugger.manager.start |
debugger.manager.start |
53 |
end |
end |
54 |
else |
else |
55 |
debugger = base_slot.manager.add |
debugger = base_slot.manager.add |
56 |
end |
end |
85 |
|
|
86 |
include DRbUndumped |
include DRbUndumped |
87 |
|
|
88 |
attr_reader :slot, :running |
attr_reader :slot, :running, :debuggee, :plugin |
89 |
|
|
90 |
## |
## |
91 |
# Instantiate a new debugger session . Only one session at a time for now |
# Instantiate a new debugger session . Only one session at a time for now |
98 |
@plugin.log_info << "Debugger session created #{base_slot.path}" |
@plugin.log_info << "Debugger session created #{base_slot.path}" |
99 |
end |
end |
100 |
|
|
101 |
|
## |
102 |
|
# Prompt a message in the status bar |
103 |
|
# |
104 |
|
def status(msg) |
105 |
|
@plugin['/system/ui/current/StatusBar/actions/prompt'].invoke(msg) |
106 |
|
end |
107 |
|
|
108 |
## |
## |
109 |
# Actually start the debugger session. Run the remote debugger, |
# Actually start the debugger session. Run the remote debugger, |
117 |
# initialize variables |
# initialize variables |
118 |
@loaded_files = Hash.new |
@loaded_files = Hash.new |
119 |
@running = false |
@running = false |
|
@breakpoints = Array.new |
|
|
@breakpoint_subscribers = Hash.new |
|
120 |
@action_queue = Array.new |
@action_queue = Array.new |
121 |
|
|
122 |
# open the debugger command pipe |
# open the debugger command pipe |
130 |
@plugin.log_info << "File to debug : #{file}" |
@plugin.log_info << "File to debug : #{file}" |
131 |
debuggee_file = File.join("#{@plugin.plugin_configuration.base_path}","debuggee.rb") |
debuggee_file = File.join("#{@plugin.plugin_configuration.base_path}","debuggee.rb") |
132 |
command = "ruby -I. -Iso -Iredist -r #{debuggee_file} #{file}" |
command = "ruby -I. -Iso -Iredist -r #{debuggee_file} #{file}" |
133 |
puts command |
puts command if DEBUG |
134 |
|
|
135 |
# On Windows/mswin32 build the popen3 method doesn't work because |
# On Windows/mswin32 build the popen3 method doesn't work because |
136 |
# the fork() is not supported |
# the fork() is not supported |
162 |
# @debugSvr.thread.join |
# @debugSvr.thread.join |
163 |
end |
end |
164 |
|
|
165 |
@paused = true |
@paused = true |
166 |
|
|
167 |
|
# create the watchpoint and breakpoint manager (only after |
168 |
|
# debuggee is running) |
169 |
|
@brk_mgr = BreakpointManager.new(self) |
170 |
|
@watch_mgr = WatchpointManager.new(self) |
171 |
|
|
172 |
# attach process stdout and err to text console |
# attach process stdout and err to text console |
173 |
@actions['attach_stderr'].invoke(@err) unless @err.nil? |
@actions['attach_stderr'].invoke(@err) unless @err.nil? |
180 |
# are received we can act accordingly on the remote debugger |
# are received we can act accordingly on the remote debugger |
181 |
@plugin['/system/ui/components/EditPane'].subscribe { |event, slot| |
@plugin['/system/ui/components/EditPane'].subscribe { |event, slot| |
182 |
if (event == :notify_slot_add && slot.parent.name == 'EditPane') |
if (event == :notify_slot_add && slot.parent.name == 'EditPane') |
183 |
breakpoint_subscribe(slot) |
@brk_mgr.subscribe(slot) |
184 |
end |
end |
185 |
} |
} |
186 |
|
|
187 |
# make sure that we also subscribe to existing edit panes if any |
# make sure that we also subscribe to existing edit panes if any |
|
|
|
188 |
@plugin['/system/ui/components/EditPane'].each_slot { |slot| |
@plugin['/system/ui/components/EditPane'].each_slot { |slot| |
189 |
breakpoint_subscribe(slot) |
@brk_mgr.subscribe(slot) |
190 |
} |
} |
|
|
|
|
@actions['start'].invoke |
|
|
@running = true |
|
|
@plugin.log_info << "Debugger session started #{@base_slot.path}" |
|
|
|
|
|
end |
|
191 |
|
|
192 |
## |
# start GUI |
193 |
# Subscribe to the breakpoints event queue of an edit pane |
@actions['start'].invoke |
|
# |
|
|
def breakpoint_subscribe(slot) |
|
194 |
|
|
195 |
# Make sure any pending add/delete brkpoints events that occured |
# get the list of watches memorized in the GUI and set them up |
196 |
# before this session is cleared |
gui_idx=0 |
197 |
slot['breakpoints'].queue.clear |
@actions['list_watchpoints'].invoke().each do |expr| |
198 |
|
@watch_mgr.add(expr,gui_idx) |
199 |
# Now subscribe |
gui_idx = gui_idx.succ |
|
sub_id = slot['breakpoints'].subscribe do |event, slot| |
|
|
if event == :notify_queue_join |
|
|
while action = slot.queue.leave |
|
|
cmd, line = action |
|
|
file = slot.parent.data |
|
|
if cmd == 'add' |
|
|
add_breakpoint(file, line) |
|
|
else |
|
|
delete_breakpoint(file, line) |
|
|
end |
|
|
end |
|
|
end |
|
200 |
end |
end |
|
@breakpoint_subscribers[slot['breakpoints']] = sub_id |
|
|
puts "dbg subscribe to #{slot.path} breakpoints" if DEBUG |
|
|
end |
|
201 |
|
|
202 |
## |
# All good now! |
203 |
# Unsubscribe from all the breakpoints event queue at once |
@running = true |
204 |
# |
@plugin.log_info << "Debugger session started #{@base_slot.path}" |
205 |
def breakpoint_unsubscribe |
status("Debugger process started (#{debugUri}, process id #{@pid})") |
|
@breakpoint_subscribers.each_pair do |slot,id| |
|
|
slot.unsubscribe(id) |
|
|
end |
|
|
@breakpoint_subscribers = {} |
|
|
end |
|
206 |
|
|
207 |
|
end |
208 |
|
|
209 |
## |
## |
210 |
# Open the pipe where the remote debugger will make a blocking |
# Open the pipe where the remote debugger will make a blocking |
234 |
@actions['detach_stdin'].invoke(@inp) |
@actions['detach_stdin'].invoke(@inp) |
235 |
close_pipe() |
close_pipe() |
236 |
show_debugline(@file,nil) |
show_debugline(@file,nil) |
237 |
breakpoint_unsubscribe() |
@brk_mgr.unsubscribe_all() |
238 |
reset_loaded_file() |
reset_loaded_file() |
239 |
@running = false |
@running = false |
240 |
|
@debuggee = SilentDebuggee.instance |
241 |
@plugin.log_info << "Debugger session stopped #{@base_slot.path}" |
@plugin.log_info << "Debugger session stopped #{@base_slot.path}" |
242 |
|
status("Debugger process exited") |
243 |
end |
end |
244 |
|
|
245 |
## |
## |
299 |
end |
end |
300 |
|
|
301 |
## |
## |
302 |
# Set up all the break points placed on a given file in the debugger |
# add a watch point |
|
# if it has never been loaded before. Subscribe to the break points slot |
|
|
# of the edit pane so any future changes in the edit pane is |
|
|
# |
|
|
def set_all_breakpoints(file) |
|
|
# Editpane properties keeps track of where the break points are placed |
|
|
# from one FR session to another. So ask the edit pane for the breakpoints |
|
|
lines = @cmd_mgr.command('EditPane/GetBreakpointsForFile').invoke(file) |
|
|
unless lines.nil? |
|
|
lines.each { |line| add_breakpoint(file,line) } |
|
|
end |
|
|
end |
|
|
|
|
|
## |
|
|
# Add a breakpoint in the remote debugger only if it hasn't been set |
|
|
# already |
|
|
# |
|
|
# temp = true means that when the breakpoint is reached we |
|
|
# delete it (used by run to cursor function) |
|
303 |
# |
# |
304 |
def add_breakpoint(file,line, temp = false) |
def add_watchpoint(expr, gui_idx) |
305 |
return unless breakpoint_index(file,line).nil? |
@watch_mgr.add(expr,gui_idx) |
|
idx = @debuggee.add_break_point(file, line, temp) |
|
|
@breakpoints[idx] = [file,line] if temp == false |
|
|
@plugin.log_info << "Set breakpoint #{idx} at #{File.basename(file)}:#{line}" |
|
306 |
end |
end |
|
|
|
|
## |
|
|
# Delete a breakpoint |
|
|
# |
|
|
def delete_breakpoint(file,line) |
|
|
if (idx = breakpoint_index(file,line)) |
|
|
@breakpoints[idx] = nil |
|
|
done = @debuggee.delete_break_point(idx) |
|
|
end |
|
|
if done |
|
|
@plugin.log_info << "Breakpoint #{idx} deleted." |
|
|
else |
|
|
@plugin.log_info << "Breakpoint #{idx} is not defined." |
|
|
end |
|
|
end |
|
|
|
|
307 |
|
|
308 |
## |
## |
309 |
# Given a file, line couple find the corresponding break point index in the |
# Delete a watch point |
|
# @breakpoint Array. For breakpoints there is no real need to replicate the |
|
|
# breakpoint list in this class because the file,line couples uniquely identifies |
|
|
# a breakpoint. Watch points however have no file,line associated with it and |
|
|
# we must keep track of their index number when created so that we can delete |
|
|
# them later on. |
|
310 |
# |
# |
311 |
def breakpoint_index(file,line) |
def delete_watchpoint(expr,gui_idx) |
312 |
@breakpoints.index [file,line] |
@watch_mgr.delete(expr,gui_idx) |
313 |
end |
end |
314 |
|
|
315 |
|
|
316 |
## |
## |
317 |
# Run debugger up to where the cursor is. The only way to do this is |
# Run debugger up to where the cursor is. The only way to do this is |
318 |
# to place a temporary breakpoint on the targeted line. Using "next nnn" |
# to place a temporary breakpoint on the targeted line. Using "next nnn" |
322 |
line = cursor_line() |
line = cursor_line() |
323 |
return if line == @line |
return if line == @line |
324 |
file = @plugin['/system/ui/current/EditPane'].data |
file = @plugin['/system/ui/current/EditPane'].data |
325 |
add_breakpoint(file, line, true) |
@brk_mgr.add(file, line, true) |
326 |
send_command('cont') |
send_command('cont') |
327 |
end |
end |
328 |
|
|
396 |
## |
## |
397 |
# Originally called by the remote debugger to print the debugger prompt and |
# Originally called by the remote debugger to print the debugger prompt and |
398 |
# wait for the next end user command typed on the keyboard. In the FreeRIDE |
# wait for the next end user command typed on the keyboard. In the FreeRIDE |
399 |
# version it simply waits for the next event to come on the "actions" pipe. |
# version it simply waits for the next debugger command |
400 |
# |
# |
401 |
def prompt( str ) |
def prompt( str ) |
402 |
|
|
429 |
end |
end |
430 |
|
|
431 |
def printf( *args ) |
def printf( *args ) |
|
stg = sprintf( *args ) |
|
|
if ( (stg =~ /(.*):(\d+):/) != nil) |
|
|
prev_file = @file |
|
|
@file = $1 |
|
|
@line = $2.to_i |
|
|
puts "File: #{@file}, line: #{@line}" if DEBUG |
|
|
|
|
|
clear_debugline(prev_file) unless prev_file == @file |
|
|
show_debugline(@file, @line) |
|
432 |
|
|
433 |
# first time we are stopping in this file? Then keep track of it |
# See debugger output |
434 |
add_loaded_file(@file) unless check_loaded_file(@file) |
stdout.printf( *args ) if DEBUG |
435 |
|
|
436 |
|
stg = sprintf( *args ) |
437 |
|
case stg |
438 |
|
when /(.*):(\d+):/ |
439 |
|
prev_file = @file |
440 |
|
@file = $1 |
441 |
|
@line = $2.to_i |
442 |
|
puts "File: #{@file}, line: #{@line}" if DEBUG |
443 |
|
|
444 |
|
clear_debugline(prev_file) unless prev_file == @file |
445 |
|
show_debugline(@file, @line) |
446 |
|
|
447 |
|
# first time we are stopping in this file? Then keep track of it |
448 |
|
add_loaded_file(@file) unless check_loaded_file(@file) |
449 |
|
|
450 |
|
when /^Breakpoint (\d+), (\w)/ |
451 |
|
idx = $1.to_i |
452 |
|
method = $2 |
453 |
|
brkpt = @brk_mgr[idx] |
454 |
|
status("Breakpoint reached at #{File.basename(brkpt.file)}, line #{brkpt.line}") |
455 |
|
|
456 |
|
when /^Watchpoint (\d+), (\w+) at (.*):(.*)$/ |
457 |
|
# caution!! sometimes the line number is instead the method |
458 |
|
# name so we cannot use \d+ in the last ( ) |
459 |
|
idx = $1.to_i |
460 |
|
method = $2 |
461 |
|
file = $3 |
462 |
|
pos = $4 |
463 |
|
expr = @watch_mgr[idx].expr |
464 |
|
status("Watchpoint reached at #{File.basename(file)}, line #{pos} (#{expr})") |
465 |
|
|
466 |
end |
end |
467 |
# LJ - Uncomment to see debugger output |
|
|
# stdout.printf( *args ) |
|
468 |
end |
end |
469 |
|
|
470 |
def print( *args ) |
def print( *args ) |
473 |
end |
end |
474 |
|
|
475 |
## |
## |
476 |
|
# Inform the debugger that the debuggee has just loaded a new file |
477 |
|
# This method is called from the debuggee process |
478 |
|
# For now we just set up breakpoints associated with this file |
479 |
|
# |
480 |
|
def file_loaded(file) |
481 |
|
@brk_mgr.set_all(file) |
482 |
|
end |
483 |
|
|
484 |
|
## |
485 |
# Update the local variable list and ask the renderer to reflect this in the UI |
# Update the local variable list and ask the renderer to reflect this in the UI |
486 |
# |
# |
487 |
def update_local_variables() |
def update_local_variables() |
532 |
# |
# |
533 |
def update_frame_list() |
def update_frame_list() |
534 |
@fr_list = @debuggee.fr_frame_list_all() |
@fr_list = @debuggee.fr_frame_list_all() |
|
#puts "fr_info #{fr_info.inspect}" |
|
535 |
@actions['update_frame_list'].invoke(@fr_list) |
@actions['update_frame_list'].invoke(@fr_list) |
536 |
end |
end |
537 |
|
|
542 |
level = @debuggee.fr_select_frame(fr_info[0]) |
level = @debuggee.fr_select_frame(fr_info[0]) |
543 |
if fr_info[0] != level |
if fr_info[0] != level |
544 |
error_msg = "ERROR!!! selected frame level #{fr_info[0]} could not be selected. Now #{level}" |
error_msg = "ERROR!!! selected frame level #{fr_info[0]} could not be selected. Now #{level}" |
|
puts error_msg |
|
545 |
@plugin.log_error << error_msg |
@plugin.log_error << error_msg |
546 |
else |
else |
547 |
@file = fr_info[1] |
@file = fr_info[1] |
565 |
"#{fr_info[0]}- #{File.basename(fr_info[1])}:#{fr_info[2]} - #{fr_info[3]}" |
"#{fr_info[0]}- #{File.basename(fr_info[1])}:#{fr_info[2]} - #{fr_info[3]}" |
566 |
end |
end |
567 |
|
|
568 |
# evaluate an expression in the current context |
## |
569 |
|
# Evaluate an expression in the current context. |
570 |
|
# |
571 |
|
# Ouput: the inspected value (not the value itself) |
572 |
def eval_expr(expr) |
def eval_expr(expr) |
573 |
@debuggee.fr_eval_expr(expr) |
# rk: remove the leading and trailing quote |
574 |
end |
@debuggee.fr_eval_expr(expr)[1..-2] |
|
|
|
|
def breakpoint_reached(idx) |
|
|
# FIXME: to be implemented (probably show a message in the status bar. Must also |
|
|
# call this method from the debuggee. |
|
575 |
end |
end |
576 |
|
|
577 |
## |
## |
590 |
close() if @action == 'CLOSE' |
close() if @action == 'CLOSE' |
591 |
# do this last because it kills the thread it runs in!! |
# do this last because it kills the thread it runs in!! |
592 |
@debugSvr.stop_service |
@debugSvr.stop_service |
593 |
|
@debuggee = SilentDebuggee.instance |
594 |
end |
end |
595 |
end |
end |
596 |
|
|
600 |
STDOUT |
STDOUT |
601 |
end |
end |
602 |
|
|
603 |
end # module Debugger |
end # class Debugger |
604 |
|
|
605 |
|
## |
606 |
|
# Silent Debuggee class used whenever the |
607 |
|
# debuggee process is stopped and we still want to |
608 |
|
# avoid exception if a remote method is called |
609 |
|
class SilentDebuggee |
610 |
|
include Singleton |
611 |
|
|
612 |
|
def fr_eval_expr(expr) |
613 |
|
return '"can\'t eval - process stopped"' |
614 |
|
end |
615 |
|
|
616 |
|
def method_missing(method_id, *args) |
617 |
|
# capture all the missing methods and do nothing |
618 |
|
end |
619 |
|
end |
620 |
|
|
621 |
end; end |
end; end |