-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Mark Moseley
committed
Oct 2, 2009
1 parent
a4f236c
commit 526545f
Showing
1 changed file
with
398 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,398 @@ | ||
#!/usr/bin/env ruby | ||
|
||
#=== Summary | ||
# | ||
#A command-line front-end to the Ruby debugger, <tt>ruby-debug</tt>, the | ||
#Fast Ruby Debugger. | ||
# | ||
#Command invocation: | ||
# | ||
# rdebug [options] [--] [script-options] ruby-script-to-debug | ||
# rdebug [options] [script-options] [--client] | ||
# rdebug [--version | --help] | ||
# | ||
#=== Options | ||
# | ||
#<tt>-A | --annotate</tt> <i>level</i>:: | ||
# Set gdb-style annotation to <i>level</i>, 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. | ||
# | ||
#<tt>--client</tt>:: | ||
# Connect to a remote debugger. Used with another rdebug invocation | ||
# using <tt>--server</tt>. See also <tt>--host</tt> and | ||
# <tt>--cport</tt> options | ||
# | ||
#<tt>--cport=</tt><i>port</i>:: | ||
# Use port <i>port</i> for access to debugger control. | ||
# | ||
#<tt>-d | --debug</tt>:: | ||
# Set $DEBUG true. | ||
# | ||
#<tt>--emacs</tt>:: | ||
# Activates full GNU Emacs mode. Is the equivalent of setting the | ||
# options <tt>--emacs-basic --annotate=3 --no-stop --no-control | ||
# --post-mortem</tt>. | ||
# | ||
#<tt>--emacs-basic</tt>:: | ||
# Activates GNU Emacs mode. Debugger prompts are prefaced with two | ||
# octal 032 characters. | ||
# | ||
#<tt>-h | --host=</tt><i>host</i>:: | ||
# Use host name <i>host</i> for remote debugging. | ||
# | ||
#<tt>-I | --include</tt> <i>path</i> | ||
# Add <i>path</i> to <tt>$LOAD_PATH</tt> | ||
# | ||
#<tt>-m | --post-mortem</tt>:: | ||
# Activate post-mortem mode. | ||
# | ||
#<tt>--no-control</tt>:: | ||
# Do not automatically start control thread. | ||
# | ||
#<tt>--no-stop</tt>:: | ||
# Do not stop when script is loaded. | ||
# | ||
#<tt>-p | --port=PORT</tt>:: | ||
# Host name used for remote debugging. | ||
# | ||
#<tt>-r | --require</tt><i>script</i>:: | ||
# Require the library, before executing your script. | ||
# | ||
#<tt>--script</tt> <i>file</i>:: | ||
# Run debugger script file <i>file</i> | ||
# | ||
#<tt>-x | --trace</tt>:: | ||
# Show lines before executing them. | ||
# | ||
#<tt>--no-quit</tt>:: | ||
# Do not quit when script terminates. Instead rerun the | ||
# program. | ||
# | ||
#<tt>--version</tt>:: | ||
# Show the version number and exit. | ||
# | ||
#<tt>--verbose</tt>:: | ||
# Turn on verbose mode. | ||
# | ||
#<tt>--v</tt>:: | ||
# 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. | ||
# | ||
#<tt>--nx</tt>:: | ||
# Don’t execute commands found in any initialization | ||
# files, e.g. <tt>.rdebugrc</tt>. | ||
# | ||
#<tt>--keep-frame-binding</tt>:: | ||
# Keep frame bindings. | ||
# | ||
#<tt>--script=</tt><i>file</i>:: | ||
# Name of the script file to run | ||
# | ||
#<tt>-s | --server</tt>:: | ||
# Listen for remote connections. Another rdebug session | ||
# accesses using the <tt>--client</tt> option. See also the | ||
# <tt>--host</tt>, <tt>--port</tt> and <tt>--cport</tt> options | ||
# | ||
#<tt>-w | --wait</tt>:: | ||
# Wait for a client connection; implies <tt>-s</tt> option. | ||
# | ||
#<tt>--help</tt>:: | ||
# 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 | ||
print bt.backtrace.map{|l| "\t#{l}"}.join("\n"), "\n" | ||
print "Uncaught exception: #{bt}\n" | ||
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, | ||
'host' => nil, | ||
'quit' => true, | ||
'no_rewrite_program' => false, | ||
'stop' => true, | ||
'nx' => false, | ||
'port' => Debugger::PORT, | ||
'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 | ||
#{program} #{Debugger::VERSION} | ||
Usage: #{program} [options] <script.rb> -- <script.rb parameters> | ||
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 | ||
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('--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 | ||
|
||
if options.server | ||
# start remote mode | ||
Debugger.start_remote(options.host, [options.port, options.cport]) 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 | ||
|
||
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 |