#!/usr/bin/env ruby #=== Summary # #A command-line front-end to the Ruby debugger, ruby-debug, the #Fast Ruby Debugger. # #Command invocation: # # rdebug [options] [--] [script-options] ruby-script-to-debug # rdebug [options] [script-options] [--client] # rdebug [--version | --help] # #=== Options # #-A | --annotate level:: # Set gdb-style annotation to level, a number. Additional # information is output automatically when program state is # changed. This can be used by front-ends such as GNU Emacs to post # this updated information without having to poll for it. # #--client:: # Connect to a remote debugger. Used with another rdebug invocation # using --server. See also --host and # --cport options # #--cport=port:: # Use port port for access to debugger control. # #-d | --debug:: # Set $DEBUG true. # #--emacs:: # Activates full GNU Emacs mode. Is the equivalent of setting the # options --emacs-basic --annotate=3 --no-stop --no-control # --post-mortem. # #--emacs-basic:: # Activates GNU Emacs mode. Debugger prompts are prefaced with two # octal 032 characters. # #-h | --host=host:: # Use host name host for remote debugging. # #-I | --include path # Add path to $LOAD_PATH # #-m | --post-mortem:: # Activate post-mortem mode. # #--no-control:: # Do not automatically start control thread. # #--no-stop:: # Do not stop when script is loaded. # #-p | --port=PORT:: # Host name used for remote debugging. # #-r | --requirescript:: # Require the library, before executing your script. # #--script file:: # Run debugger script file file # #-x | --trace:: # Show lines before executing them. # #--no-quit:: # Do not quit when script terminates. Instead rerun the # program. # #--version:: # Show the version number and exit. # #--verbose:: # Turn on verbose mode. # #--v:: # Print the version number, then turn on verbose mode if # a script name is given. If no script name is given # just exit after printing the version number. # #--nx:: # Don’t execute commands found in any initialization # files, e.g. .rdebugrc. # #--keep-frame-binding:: # Keep frame bindings. # #--script=file:: # Name of the script file to run # #-s | --server:: # Listen for remote connections. Another rdebug session # accesses using the --client option. See also the # --host, --port and --cport options # #-w | --wait:: # Wait for a client connection; implies -s option. # #--help:: # Show invocation help and exit. require 'rubygems' require 'optparse' require 'ostruct' require_relative '../cli/ruby-debug' def debug_program(options) # Make sure Ruby script syntax checks okay. # Otherwise we get a load message that looks like rdebug has # a problem. output = `ruby -c "#{Debugger::PROG_SCRIPT}" 2>&1` if $?.exitstatus != 0 and RUBY_PLATFORM !~ /mswin/ puts output exit $?.exitstatus end print "\032\032starting\n" if Debugger.annotate and Debugger.annotate > 2 # Record where we are we can know if the call stack has been # truncated or not. Debugger.start_sentinal=caller(0)[1] bt = Debugger.debug_load(Debugger::PROG_SCRIPT, options.stop, false) if bt if options.post_mortem Debugger.handle_post_mortem(bt) else print bt.backtrace.map{|l| "\t#{l}"}.join("\n"), "\n" print "Uncaught exception: #{bt}\n" end end end # Do a shell-like path lookup for prog_script and return the results. # If we can't find anything return prog_script. def whence_file(prog_script) if prog_script.index(File::SEPARATOR) # Don't search since this name has path separator components return prog_script end for dirname in ENV['PATH'].split(File::PATH_SEPARATOR) do prog_script_try = File.join(dirname, prog_script) return prog_script_try if File.exist?(prog_script_try) end # Failure return prog_script end options = OpenStruct.new( 'annotate' => Debugger.annotate, 'client' => false, 'control' => true, 'cport' => Debugger::PORT + 1, 'frame_bind' => false, 'host' => nil, 'quit' => true, 'no_rewrite_program' => false, 'stop' => true, 'nx' => false, 'port' => Debugger::PORT, 'post_mortem' => false, 'restart_script' => nil, 'script' => nil, 'server' => false, 'tracing' => false, 'verbose_long' => false, 'wait' => false ) def process_options(options) program = File.basename($0) opts = OptionParser.new do |opts| opts.banner = < -- EOB opts.separator "" opts.separator "Options:" opts.on("-A", "--annotate LEVEL", Integer, "Set annotation level") do |annotate| Debugger.annotate = annotate end opts.on("-c", "--client", "Connect to remote debugger") do options.client = true end opts.on("--cport PORT", Integer, "Port used for control commands") do |cport| options.cport = cport end opts.on("-d", "--debug", "Set $DEBUG=true") {$DEBUG = true} opts.on("--emacs LEVEL", Integer, "Activates full Emacs support at annotation level LEVEL") do |level| Debugger.annotate = level.to_i ENV['EMACS'] = '1' ENV['COLUMNS'] = '120' if ENV['COLUMNS'].to_i < 120 options.control = false options.quit = false options.post_mortem = true end opts.on('--emacs-basic', 'Activates basic Emacs mode') do ENV['EMACS'] = '1' end opts.on('-h', '--host HOST', 'Host name used for remote debugging') do |host| options.host = host end opts.on('-I', '--include PATH', String, 'Add PATH to $LOAD_PATH') do |path| $LOAD_PATH.unshift(path) end opts.on('--keep-frame-binding', 'Keep frame bindings') do options.frame_bind = true end opts.on('-m', '--post-mortem', 'Activate post-mortem mode') do options.post_mortem = true end opts.on('--no-control', 'Do not automatically start control thread') do options.control = false end opts.on('--no-quit', 'Do not quit when script finishes') do options.quit = false end opts.on('--no-rewrite-program', 'Do not set $0 to the program being debugged') do options.no_rewrite_program = true end opts.on('--no-stop', 'Do not stop when script is loaded') do options.stop = false end opts.on('-nx', 'Not run debugger initialization files (e.g. .rdebugrc') do options.nx = true end opts.on('-p', '--port PORT', Integer, 'Port used for remote debugging') do |port| options.port = port end opts.on('-r', '--require SCRIPT', String, 'Require the library, before executing your script') do |name| if name == 'debug' puts "ruby-debug is not compatible with Ruby's 'debug' library. This option is ignored." else require name end end opts.on('--restart-script FILE', String, 'Name of the script file to run. Erased after read') do |restart_script| options.restart_script = restart_script unless File.exists?(options.restart_script) puts "Script file '#{options.restart_script}' is not found" exit end end opts.on('--script FILE', String, 'Name of the script file to run') do |script| options.script = script unless File.exists?(options.script) puts "Script file '#{options.script}' is not found" exit end end opts.on('-s', '--server', 'Listen for remote connections') do options.server = true end opts.on('-w', '--wait', 'Wait for a client connection, implies -s option') do options.wait = true end opts.on('-x', '--trace', 'Turn on line tracing') {options.tracing = true} opts.separator '' opts.separator 'Common options:' opts.on_tail('--help', 'Show this message') do puts opts exit end opts.on_tail('--version', 'Print the version') do puts "ruby-debug #{Debugger::VERSION}" exit end opts.on('--verbose', 'Turn on verbose mode') do $VERBOSE = true options.verbose_long = true end opts.on_tail('-v', 'Print version number, then turn on verbose mode') do puts "ruby-debug #{Debugger::VERSION}" $VERBOSE = true end end return opts end # What file is used for debugger startup commands. unless defined?(OPTS_INITFILE) if RUBY_PLATFORM =~ /mswin/ # Of course MS Windows has to be different OPTS_INITFILE = 'rdbopt.ini' HOME_DIR = (ENV['HOME'] || ENV['HOMEDRIVE'].to_s + ENV['HOMEPATH'].to_s).to_s else OPTS_INITFILE = '.rdboptrc' HOME_DIR = ENV['HOME'].to_s end end begin initfile = File.join(HOME_DIR, OPTS_INITFILE) eval(File.read(initfile)) if File.exist?(initfile) rescue end opts = process_options(options) begin if not defined? Debugger::ARGV Debugger::ARGV = ARGV.clone end rdebug_path = File.expand_path($0) if RUBY_PLATFORM =~ /mswin/ rdebug_path += '.cmd' unless rdebug_path =~ /\.cmd$/i end Debugger::RDEBUG_SCRIPT = rdebug_path Debugger::RDEBUG_FILE = __FILE__ Debugger::INITIAL_DIR = Dir.pwd opts.parse! ARGV rescue StandardError => e puts opts puts puts e.message exit(-1) end if options.client Debugger.start_client(options.host, options.port) else if ARGV.empty? exit if $VERBOSE and not options.verbose_long puts opts puts puts 'Must specify a script to run' exit(-1) end # save script name prog_script = ARGV.shift prog_script = whence_file(prog_script) unless File.exist?(prog_script) Debugger::PROG_SCRIPT = File.expand_path prog_script # install interruption handler trap('INT') { Debugger.interrupt_last } # set options Debugger.wait_connection = options.wait Debugger.keep_frame_binding = options.frame_bind if options.server # start remote mode Debugger.start_remote(options.host, [options.port, options.cport], options.post_mortem) do # load initrc script Debugger.run_init_script(StringIO.new) unless options.nx end debug_program(options) else # Set up trace hook for debugger Debugger.start # start control thread Debugger.start_control(options.host, options.cport) if options.control # load initrc script (e.g. .rdebugrc) Debugger.run_init_script(StringIO.new) unless options.nx # run restore-settings startup script if specified if options.restart_script require 'fileutils' Debugger.run_script(options.restart_script) FileUtils.rm(options.restart_script) end # run startup script if specified if options.script Debugger.run_script(options.script) end # activate post-mortem Debugger.post_mortem if options.post_mortem options.stop = false if options.tracing Debugger.tracing = options.tracing if !options.quit if Debugger.started? until Debugger.stop do end end begin debug_program(options) rescue SyntaxError puts $!.backtrace.map{|l| "\t#{l}"}.join("\n") puts "Uncaught Syntax Error\n" rescue print $!.backtrace.map{|l| "\t#{l}"}.join("\n"), "\n" print "Uncaught exception: #{$!}\n" end print "The program finished.\n" unless Debugger.annotate.to_i > 1 # annotate has its own way interface = Debugger::LocalInterface.new # Not sure if ControlCommandProcessor is really the right # thing to use. CommandProcessor requires a state. processor = Debugger::ControlCommandProcessor.new(interface) processor.process_commands else debug_program(options) end end end