#!/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