Browse files

initial import

git-svn-id: svn://rubyforge.org/var/svn/ruby-debug/trunk@1 453b2852-3d18-0410-866e-d09c099698e4
  • Loading branch information...
0 parents commit 49b961c87f3a4b430a773e98c3150876a7c1d475 kent committed Oct 9, 2006
171 .gdb_history
@@ -0,0 +1,171 @@
+r
+q
+file ruby
+set args '-rruby_debug test.rb'
+r
+set args '-r../ruby_debug ../test.rb'
+r
+q
+attach 6275
+c
+where
+p breakpoints
+p RARRAY(breakpoints)->len
+q
+file ruby
+set args -rruby_debug test.rb
+r
+where
+p offset
+frame 1
+p threads_tbl
+p/x threads_tbl
+frame 3
+context
+p context
+p debug_context
+p node->nd_file
+line
+p line
+q
+attach
+attach 7230
+c
+where
+q
+attach 23918
+c
+where
+q
+file ruby
+set args '-I./lib/:./ext/ -rruby-debug test.rb'
+r
+q
+help
+q
+file ruby
+set args -Iext:lib ./bin/rdebug t.rb
+r
+w
+where
+q
+attach ruby.781
+b ruby_debug.c:454
+c
+attach ruby.808
+b ruby_debug.c:453
+c
+c
+b
+help breakpoint
+clear
+b
+clear
+clear
+help
+help breakpoints
+b ruby_debug.c:452
+c
+l
+l -
+w
+where
+step
+fin
+step
+b 466
+c
+q
+help
+help breakpoints
+help save-breakpoints
+exit
+quit
+file ruby
+set args -Iext:lib bin/rdebug t2.rb
+c
+r
+b ruby_debug.c:366
+del 1
+b
+del 1
+del 2
+b
+del 3
+b ruby_debug.c:364
+c
+c
+c
+c
+c
+c
+c
+c
+c
+c
+del 4
+c
+q
+file ruby
+set args -Iext:lib bin/rdebug t2.rb
+c
+r
+b ruby_debug.c:814
+c
+c
+breakpoints
+help breakpoints
+help show
+show breapoints
+show breapoint
+show breakpoint
+show breakpoints
+f
+w
+bt
+p current
+p context
+c
+del 1
+b ruby_debug.c:810
+c
+r
+c
+s
+fin
+p current
+p context
+s
+s
+s
+fin
+help stepping
+help
+help running
+n
+p current
+p context
+n
+p saved_crit
+p saved_crit
+n
+n
+q
+file ruby
+set args -Iext:lib bin/rdebug t2.rb
+b rb_thread_schedule
+del 1
+c
+r
+b rb_thread_schedule
+c
+c
+del
+c
+q
+file ruby
+set args -Iext:lib bin/rdebug t2.rb
+r
+c
+r
+q
64 CHANGES
@@ -0,0 +1,64 @@
+0.4.3
+- Bugfixes.
+
+0.4.2
+- Module#deubg_method added.
+- Added rdoc.
+- Bugfixes.
+
+0.4.1
+- New binding_n method for Kernel module.
+- Bugfixes.
+
+0.4
+- Debugger.start method takes a block. If a block is specified, this method starts debugger, yields to the block
+ and stops debugger at the end.
+- 'tm[ate]' command accepts a frame number now.
+- 'list' command accepts on/off parameter which controls whether listing will be displayed on every stop.
+- 'eval on/off' controls the evaluation of unknown command.
+- Debugger reads readline history file .rdebug_hist at startup and saves it at exit.
+- 'sa[ve] <file>' command can be used to save current breackpoints and catchpoint if any
+- 'sc[ript] <file>' command can be used to run script file. Script files can contain only control commands.
+- rdebug script accepts '--script FILE' parameter.
+- thread commands are available for the control port.
+
+0.3 (2006-08-07)
+- Renamed Debugger.start_server to Debugger.start_remote.
+- Debugger.start_remote activates debugger by calling Debugger.start.
+- Debugger.start_remote starts a control thread which listen on port 8990 and accepts control
+ commands, such as adding/deleting breakpoints, assigning catchpoint, etc. (Useful for GUI integration)
+- New Debugger.wait_connection option. When it's true, Debugger.start_remote waits until
+ a remote connection is made.
+- New Debugger.stop_on_connect option. When a remote connection is established, debugger
+ stops the main thread (Thread.main).
+- 'interrupt' command is available for the control thread.
+
+0.2.1 (2006-07-29)
+- 'f[rame] nn' command selects a numbered frame. Frame numbers can be obtained by running frame
+ command without parameters.
+- 'l[ist] =' show code in the context of the current line.
+- 'tm[ate]' opens the current file in TextMate. Available only on Mac OSX.
+
+0.2 (2006-07-17)
+- Added the remote debugging. It should be activated by calling Debugger#start_server method.
+- CHANGED: In order to activate the debugger, it's not enough to require 'ruby-debug'.
+ Debugger#start method must be called explicitly.
+- Debugger used to evaluate anything you enter as long as it's not a command. Starting from
+ this version the 'eval' command must be used to evaluate an expression.
+
+0.1.5 (2006-07-13)
+- Now the check for a breakpoint uses base filename of the source file.
+- Removed compilation warnings when compiling with -Wall
+
+0.1.4 (2006-07-12)
+- Remembers the previous command. Invoke it by typing a carriage return
+ at the command prompt.
+
+0.1.3 (2006-07-11)
+- Conditional breakpoints
+- Bugfixes
+
+0.1.2 (2006-07-16)
+========================
+
+- Initial release.
23 LICENSE
@@ -0,0 +1,23 @@
+Copyright (C) 2005 Kent Sibilev <ksibilev@yahoo.com>
+All rights reserved.
+ *
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ *
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
63 README
@@ -0,0 +1,63 @@
+= ruby-debug
+
+== Overview
+
+ruby-debug is a fast implementation of the standard debugger debug.rb.
+The faster execution speed is achieved by utilizing a new hook Ruby C API.
+
+== Requirements
+
+ruby-debug requires Ruby 1.8.4 or higher.
+
+If you are running Linux or Unix you'll need a C compiler so the extension
+can be compiled when it is installed.
+
+
+== Install
+
+ruby-debug is provided as a RubyGem. To install:
+
+<tt>gem install ruby-debug</tt>
+
+== Usage
+
+There are two ways of running ruby-debug.
+
+=== rdebug executable:
+
+$ rdebug <your-script>
+
+When you start your script this way, the debugger will stop at
+the first line of code in the script file. So you will be able
+to set up your breakpoints.
+
+=== ruby-debug API
+
+The second way is to use the ruby-debug API to interrupt your
+code execution at runtime.
+
+ require 'ruby-debug'
+ ...
+ def your_method
+ ...
+ debugger
+ ...
+ end
+
+When Kernel#debugger method is executed, the debugger is activated
+and you will be able to inspect and step through your code.
+
+== Performance
+
+The debug.rb script that comes with the standard library uses
+Kernel#set_trace_func API. This way it is possible to implement
+the debugger in pure Ruby, but has a negative effect on the speed
+of your program execution. For each trace call Ruby interpreter
+creates a Binding object, even though it is not being used most
+of the time. ruby-debug library moves most of the functionality
+of debug.rb to a native extension, this way significantly improving
+the execution of your program.
+
+== License
+
+See LICENSE for license information.
119 Rakefile
@@ -0,0 +1,119 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+require 'rake/rdoctask'
+
+SO_NAME = "ruby_debug.so"
+
+# ------- Default Package ----------
+RUBY_DEBUG_VERSION = "0.4.3"
+
+FILES = FileList[
+ 'Rakefile',
+ 'README',
+ 'LICENSE',
+ 'CHANGES',
+ 'lib/**/*',
+ 'ext/*',
+ 'doc/*',
+ 'bin/*'
+]
+
+# Default GEM Specification
+default_spec = Gem::Specification.new do |spec|
+ spec.name = "ruby-debug"
+
+ spec.homepage = "http://rubyforge.org/projects/ruby-debug/"
+ spec.summary = "Fast Ruby debugger"
+ spec.description = <<-EOF
+ruby-debug is a fast implementation of the standard Ruby debugger debug.rb.
+It's implemented by utilizing a new hook Ruby C API.
+EOF
+
+ spec.version = RUBY_DEBUG_VERSION
+
+ spec.author = "Kent Sibilev"
+ spec.email = "ksibilev@yahoo.com"
+ spec.platform = Gem::Platform::RUBY
+ spec.require_path = "lib"
+ spec.bindir = "bin"
+ spec.executables = ["rdebug"]
+ spec.extensions = ["ext/extconf.rb"]
+ spec.autorequire = "ruby-debug"
+ spec.files = FILES.to_a
+
+ spec.required_ruby_version = '>= 1.8.2'
+ spec.date = DateTime.now
+ spec.rubyforge_project = 'ruby-debug'
+
+ # rdoc
+ spec.has_rdoc = true
+end
+
+# Rake task to build the default package
+Rake::GemPackageTask.new(default_spec) do |pkg|
+ pkg.need_tar = true
+ pkg.need_tar = true
+end
+
+task :default => [:package]
+
+# Windows specification
+win_spec = default_spec.clone
+win_spec.extensions = []
+win_spec.platform = Gem::Platform::WIN32
+win_spec.files += ["lib/#{SO_NAME}"]
+
+desc "Create Windows Gem"
+task :win32_gem do
+ # Copy the win32 extension the top level directory
+ current_dir = File.expand_path(File.dirname(__FILE__))
+ source = File.join(current_dir, "ext", "win32", SO_NAME)
+ target = File.join(current_dir, "lib", SO_NAME)
+ cp(source, target)
+
+ # Create the gem, then move it to pkg
+ Gem::Builder.new(win_spec).build
+ gem_file = "#{win_spec.name}-#{win_spec.version}-#{win_spec.platform}.gem"
+ mv(gem_file, "pkg/#{gem_file}")
+
+ # Remove win extension fro top level directory
+ rm(target)
+end
+
+
+desc "Publish ruby-debug to RubyForge."
+task :publish do
+ require 'rake/contrib/sshpublisher'
+
+ # Get ruby-debug path
+ ruby_debug_path = File.expand_path(File.dirname(__FILE__))
+
+ publisher = Rake::SshDirPublisher.new("kent@rubyforge.org",
+ "/var/www/gforge-projects/ruby-debug", ruby_debug_path)
+end
+
+desc "Clear temp files"
+task :clean do
+ cd "ext" do
+ if File.exists?("Makefile")
+ sh "make clean"
+ rm "Makefile"
+ end
+ end
+end
+
+# --------- RDoc Documentation ------
+desc "Generate rdoc documentation"
+Rake::RDocTask.new("rdoc") do |rdoc|
+ rdoc.rdoc_dir = 'doc'
+ rdoc.title = "ruby-debug"
+ # Show source inline with line numbers
+ rdoc.options << "--inline-source" << "--line-numbers"
+ # Make the readme file the start page for the generated html
+ rdoc.options << '--main' << 'README'
+ rdoc.rdoc_files.include('bin/**/*',
+ 'lib/**/*.rb',
+ 'ext/**/ruby_debug.c',
+ 'README',
+ 'LICENSE')
+end
85 bin/rdebug
@@ -0,0 +1,85 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'optparse'
+require "ostruct"
+require 'ruby-debug'
+
+options = OpenStruct.new(
+ 'server' => false,
+ 'client' => false,
+ 'host' => nil,
+ 'port' => Debugger::PORT,
+ 'cport' => Debugger::PORT + 1,
+ 'wait' => false,
+ 'nostop' => false,
+ 'script' => nil
+)
+
+opts = OptionParser.new do |opts|
+ opts.banner = <<EOB
+ruby-debug #{Debugger::VERSION}
+Usage: rdebug [options] <script.rb> -- <script.rb parameters>
+EOB
+ opts.separator ""
+ opts.separator "Options:"
+ opts.on("-s", "--server", "Listen for remote connections") {options.server = true}
+ opts.on("-w", "--wait", "Wait for a client connection, implies -s option") {options.wait = true}
+ opts.on("-n", "--nostop", "Do not stop when a client connects, implies -s option") {options.nostop = true}
+ opts.on("-c", "--client", "Connect to remote debugger") {options.client = true}
+ opts.on("-h", "--host HOST", "Host name used for remote debugging") {|options.host|}
+ opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|options.port|}
+ opts.on("--script FILE", String, "Name of the script file to run") do |options.script|
+ unless File.exists?(options.script)
+ puts "Script file '#{options.script}' is not found"
+ exit
+ end
+ end
+ opts.on("--cport PORT", Integer, "Port used for contol commands, implies -s option") {|options.port|}
+ opts.separator ""
+ opts.separator "Common options:"
+ opts.on_tail("--help", "Show this message") do
+ puts opts
+ exit
+ end
+ opts.on_tail("-v", "--version", "Show version") do
+ puts "ruby-debug #{Debugger::VERSION}"
+ exit
+ end
+end
+
+begin
+ 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?
+ puts opts
+ puts
+ puts "Must specify a script to run"
+ exit(-1)
+ end
+
+ trap('INT') { Debugger.interrupt_last }
+ Debugger.stop_on_connect = !options.nostop
+ Debugger.wait_connection = options.wait
+ load "#{ENV["HOME"]}/.rdebugrc" if File.exists?("#{ENV["HOME"]}/.rdebugrc")
+ if options.server
+ Debugger.start_remote(options.host, [options.port, options.cport])
+ Debugger.debug_load ARGV.shift
+ else
+ Debugger.start
+ if options.script
+ Debugger.run_script(options.script)
+ end
+ debugger 2
+ Debugger.debug_load ARGV.shift
+ end
+end
22 ext/.gdb_history
@@ -0,0 +1,22 @@
+attach 6038
+c
+where
+q
+attach 28366
+b ruby_debug.c:207
+c
+l
+n
+l
+help running
+finish
+s
+l
+s
+c
+l
+p klass
+p debug_breakpoint
+p (debug_context_t*)debug_breakpoint
+p debug_breakpoint->source
+q
142 ext/Makefile
@@ -0,0 +1,142 @@
+
+SHELL = /bin/sh
+
+#### Start of system configuration section. ####
+
+srcdir = .
+topdir = /usr/local/lib/ruby/1.8/i686-darwin8.7.1
+hdrdir = $(topdir)
+VPATH = $(srcdir):$(topdir):$(hdrdir)
+prefix = $(DESTDIR)/usr/local
+exec_prefix = $(prefix)
+sitedir = $(prefix)/lib/ruby/site_ruby
+rubylibdir = $(libdir)/ruby/$(ruby_version)
+archdir = $(rubylibdir)/$(arch)
+sbindir = $(exec_prefix)/sbin
+datadir = $(prefix)/share
+includedir = $(prefix)/include
+infodir = $(prefix)/info
+sysconfdir = $(prefix)/etc
+mandir = $(prefix)/man
+libdir = $(exec_prefix)/lib
+sharedstatedir = $(prefix)/com
+oldincludedir = $(DESTDIR)/usr/include
+sitearchdir = $(sitelibdir)/$(sitearch)
+bindir = $(exec_prefix)/bin
+localstatedir = $(prefix)/var
+sitelibdir = $(sitedir)/$(ruby_version)
+libexecdir = $(exec_prefix)/libexec
+
+CC = gcc
+LIBRUBY = $(LIBRUBY_A)
+LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
+LIBRUBYARG_SHARED =
+LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static
+
+RUBY_EXTCONF_H =
+CFLAGS = -Wall -fno-common -g -O2 -pipe -fno-common
+INCFLAGS = -I. -I$(topdir) -I$(hdrdir) -I$(srcdir)
+CPPFLAGS =
+CXXFLAGS = $(CFLAGS)
+DLDFLAGS =
+LDSHARED = cc -dynamic -bundle -undefined suppress -flat_namespace
+AR = ar
+EXEEXT =
+
+RUBY_INSTALL_NAME = ruby
+RUBY_SO_NAME = ruby
+arch = i686-darwin8.7.1
+sitearch = i686-darwin8.7.1
+ruby_version = 1.8
+ruby = /usr/local/bin/ruby
+RUBY = $(ruby)
+RM = rm -f
+MAKEDIRS = mkdir -p
+INSTALL = /usr/bin/install -c
+INSTALL_PROG = $(INSTALL) -m 0755
+INSTALL_DATA = $(INSTALL) -m 644
+COPY = cp
+
+#### End of system configuration section. ####
+
+preload =
+
+libpath = $(libdir)
+LIBPATH = -L"$(libdir)"
+DEFFILE =
+
+CLEANFILES =
+DISTCLEANFILES =
+
+extout =
+extout_prefix =
+target_prefix =
+LOCAL_LIBS =
+LIBS = -lpthread -ldl -lobjc
+SRCS = ruby_debug.c
+OBJS = ruby_debug.o
+TARGET = ruby_debug
+DLLIB = $(TARGET).bundle
+EXTSTATIC =
+STATIC_LIB =
+
+RUBYCOMMONDIR = $(sitedir)$(target_prefix)
+RUBYLIBDIR = $(sitelibdir)$(target_prefix)
+RUBYARCHDIR = $(sitearchdir)$(target_prefix)
+
+TARGET_SO = $(DLLIB)
+CLEANLIBS = $(TARGET).bundle $(TARGET).il? $(TARGET).tds $(TARGET).map
+CLEANOBJS = *.o *.a *.s[ol] *.pdb *.exp *.bak
+
+all: $(DLLIB)
+static: $(STATIC_LIB)
+
+clean:
+ @-$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES)
+
+distclean: clean
+ @-$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
+ @-$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
+
+realclean: distclean
+install: install-so install-rb
+
+install-so: $(RUBYARCHDIR)
+install-so: $(RUBYARCHDIR)/$(DLLIB)
+$(RUBYARCHDIR)/$(DLLIB): $(DLLIB)
+ $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
+install-rb: pre-install-rb install-rb-default
+install-rb-default: pre-install-rb-default
+pre-install-rb: Makefile
+pre-install-rb-default: Makefile
+$(RUBYARCHDIR):
+ $(MAKEDIRS) $@
+
+site-install: site-install-so site-install-rb
+site-install-so: install-so
+site-install-rb: install-rb
+
+.SUFFIXES: .c .m .cc .cxx .cpp .C .o
+
+.cc.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
+
+.cxx.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
+
+.cpp.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
+
+.C.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
+
+.c.o:
+ $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) -c $<
+
+$(DLLIB): $(OBJS)
+ @-$(RM) $@
+ $(LDSHARED) $(DLDFLAGS) $(LIBPATH) -o $@ $(OBJS) $(LOCAL_LIBS) $(LIBS)
+
+
+
+$(OBJS): ruby.h defines.h
18 ext/extconf.rb
@@ -0,0 +1,18 @@
+require "mkmf"
+
+if RUBY_VERSION >= "1.9"
+ if RUBY_RELEASE_DATE < "2005-03-17"
+ STDERR.print("Ruby version is too old\n")
+ exit(1)
+ end
+elsif RUBY_VERSION >= "1.8"
+ if RUBY_RELEASE_DATE < "2005-03-22"
+ STDERR.print("Ruby version is too old\n")
+ exit(1)
+ end
+else
+ STDERR.print("Ruby version is too old\n")
+ exit(1)
+end
+
+create_makefile("ruby_debug")
BIN ext/ruby_debug.bundle
Binary file not shown.
1,386 ext/ruby_debug.c
@@ -0,0 +1,1386 @@
+#include <stdio.h>
+#include <ruby.h>
+#include <node.h>
+#include <rubysig.h>
+#include <st.h>
+
+#define DEBUG_VERSION "0.4.3"
+
+typedef struct {
+ int thnum;
+ VALUE last_file;
+ VALUE last_line;
+ int moved;
+ int stop_next;
+ int dest_frame;
+ int stop_line;
+ int stop_frame;
+ int suspend;
+ int tracing;
+ VALUE frames;
+ VALUE thread;
+} debug_context_t;
+
+typedef struct {
+ VALUE file;
+ VALUE line;
+ VALUE binding;
+ ID id;
+} debug_frame_t;
+
+typedef struct {
+ VALUE source;
+ VALUE pos;
+ VALUE expr;
+} debug_breakpoint_t;
+
+static VALUE threads_tbl = Qnil;
+static VALUE breakpoints = Qnil;
+static VALUE catchpoint = Qnil;
+static VALUE waiting = Qnil;
+static VALUE tracing = Qfalse;
+static VALUE locker = Qnil;
+
+static VALUE mDebugger;
+static VALUE cContext;
+static VALUE cFrame;
+static VALUE cBreakpoint;
+
+static VALUE file_separator;
+static VALUE alt_file_separator;
+
+static ID idAtLine;
+static ID idAtBreakpoint;
+static ID idAtCatchpoint;
+static ID idAtTracing;
+static ID idBinding;
+static ID idBasename;
+static ID idEval;
+static ID idList;
+static ID idClear;
+static ID idIndex;
+
+static int start_count = 0;
+static int thnum_max = 0;
+static int last_debugged_thnum = -1;
+
+static VALUE debug_suspend(VALUE);
+static VALUE create_binding(VALUE);
+static VALUE debug_stop(VALUE);
+
+extern int ruby_in_compile;
+
+typedef struct locked_thread_t {
+ VALUE thread;
+ struct locked_thread_t *next;
+} locked_thread_t;
+
+static locked_thread_t *locked_head = NULL;
+static locked_thread_t *locked_tail = NULL;
+
+static int
+is_in_locked(VALUE thread)
+{
+ locked_thread_t *node;
+
+ if(!locked_head)
+ return 0;
+
+ for(node = locked_head; node != locked_tail; node = node->next)
+ {
+ if(node->thread == thread) return 1;
+ }
+}
+
+static void
+add_to_locked(VALUE thread)
+{
+ locked_thread_t *node;
+
+ if(is_in_locked(thread))
+ return;
+
+ node = ALLOC(locked_thread_t);
+ node->thread = thread;
+ node->next = NULL;
+ if(locked_tail)
+ locked_tail->next = node;
+ locked_tail = node;
+ if(!locked_head)
+ locked_head = node;
+}
+
+static VALUE
+remove_from_locked()
+{
+ VALUE thread;
+ locked_thread_t *node;
+
+ if(locked_head == NULL)
+ return Qnil;
+ node = locked_head;
+ locked_head = locked_head->next;
+ if(locked_tail == node)
+ locked_tail = NULL;
+ thread = node->thread;
+ xfree(node);
+ return thread;
+}
+
+#define IS_STARTED (threads_tbl != Qnil)
+
+/*
+ * call-seq:
+ * Debugger.started? -> bool
+ *
+ * Returns +true+ the debugger is started.
+ */
+static VALUE
+debug_is_started(VALUE self)
+{
+ return IS_STARTED ? Qtrue : Qfalse;
+}
+
+static void
+debug_check_started()
+{
+ if(!IS_STARTED)
+ {
+ rb_raise(rb_eRuntimeError, "Debugger.start is not called yet.");
+ }
+}
+
+static void
+debug_context_mark(void* data)
+{
+ debug_context_t *debug_context = (debug_context_t *)data;
+ rb_gc_mark(debug_context->frames);
+ rb_gc_mark(debug_context->thread);
+ rb_gc_mark(debug_context->last_file);
+ rb_gc_mark(debug_context->last_line);
+}
+
+static VALUE
+debug_context_create(VALUE thread)
+{
+ VALUE result;
+ debug_context_t *debug_context;
+
+ debug_context = ALLOC(debug_context_t);
+ debug_context-> thnum = ++thnum_max;
+
+ debug_context->last_file = Qnil;
+ debug_context->last_line = Qnil;
+ debug_context->moved = 0;
+
+ debug_context->stop_next = -1;
+ debug_context->dest_frame = -1;
+ debug_context->stop_line = -1;
+ debug_context->stop_frame = -1;
+ debug_context->suspend = 0;
+ debug_context->tracing = 0;
+ debug_context->frames = rb_ary_new();
+ debug_context->thread = thread;
+ result = Data_Wrap_Struct(cContext, debug_context_mark, xfree, debug_context);
+ return result;
+}
+
+static VALUE
+thread_context_lookup(VALUE thread)
+{
+ VALUE context;
+
+ debug_check_started();
+
+ context = rb_hash_aref(threads_tbl, thread);
+ if(context == Qnil)
+ {
+ context = debug_context_create(thread);
+ rb_hash_aset(threads_tbl, thread, context);
+ }
+ return context;
+}
+
+static void
+debug_frame_mark(void *data)
+{
+ debug_frame_t *debug_frame = (debug_frame_t *)data;
+ rb_gc_mark(debug_frame->binding);
+ rb_gc_mark(debug_frame->file);
+ rb_gc_mark(debug_frame->line);
+}
+
+static VALUE
+debug_frame_create(VALUE file, VALUE line, VALUE binding, ID id)
+{
+ VALUE result;
+ debug_frame_t *debug_frame;
+
+ debug_frame = ALLOC(debug_frame_t);
+ debug_frame->file = file;
+ debug_frame->line = line;
+ debug_frame->binding = binding;
+ debug_frame->id = id;
+ result = Data_Wrap_Struct(cFrame, debug_frame_mark, xfree, debug_frame);
+
+ return result;
+}
+
+static void
+save_current_position(VALUE context)
+{
+ VALUE cur_frame;
+ debug_context_t *debug_context;
+ debug_frame_t *debug_frame;
+
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ if(RARRAY(debug_context->frames)->len == 0)
+ return;
+
+ cur_frame = *RARRAY(debug_context->frames)->ptr;
+ Data_Get_Struct(cur_frame, debug_frame_t, debug_frame);
+
+ debug_context->last_file = debug_frame->file;
+ debug_context->last_line = debug_frame->line;
+ debug_context->moved = 0;
+}
+
+#define did_moved() (debug_context->last_line != line || \
+ debug_context->last_file == Qnil || \
+ rb_str_cmp(debug_context->last_file, file) != 0)
+
+static VALUE
+call_at_line_unprotected(VALUE args)
+{
+ VALUE context;
+ context = *RARRAY(args)->ptr;
+ return rb_funcall2(context, idAtLine, RARRAY(args)->len - 1, RARRAY(args)->ptr + 1);
+}
+
+static VALUE
+call_at_line(VALUE context, int thnum, VALUE binding, VALUE file, VALUE line)
+{
+ VALUE args;
+
+ last_debugged_thnum = thnum;
+ save_current_position(context);
+ debug_suspend(mDebugger);
+
+ args = rb_ary_new3(4, context, file, line, binding);
+ return rb_protect(call_at_line_unprotected, args, 0);
+}
+
+static void
+set_frame_source(debug_context_t *debug_context, VALUE file, VALUE line)
+{
+ VALUE frame;
+ debug_frame_t *top_frame;
+
+ if(RARRAY(debug_context->frames)->len > 0)
+ {
+ frame = *RARRAY(debug_context->frames)->ptr;
+ Data_Get_Struct(frame, debug_frame_t, top_frame);
+ top_frame->file = file;
+ top_frame->line = line;
+ }
+}
+
+static void
+save_call_frame(VALUE self, VALUE file, VALUE line, ID mid, debug_context_t *debug_context)
+{
+ VALUE frame, binding;
+
+ binding = self? create_binding(self) : Qnil;
+ frame = debug_frame_create(file, line, binding, mid);
+ rb_ary_unshift(debug_context->frames, frame);
+}
+
+static VALUE
+basename(VALUE filename)
+{
+ return rb_funcall(rb_cFile, idBasename, 1, filename);
+}
+
+static int
+filename_cmp(debug_breakpoint_t *debug_breakpoint, VALUE file)
+{
+ VALUE flag = Qnil;
+
+ flag = rb_funcall(debug_breakpoint->source, idIndex, 1, file_separator);
+ if(alt_file_separator != Qnil && flag == Qnil)
+ flag = rb_funcall(debug_breakpoint->source, idIndex, 1, alt_file_separator);
+ if(flag == Qnil)
+ file = basename(file);
+ else
+ file = rb_file_expand_path(file, Qnil);
+ return(rb_str_cmp(debug_breakpoint->source, file) == 0);
+}
+
+static int
+classname_cmp(debug_breakpoint_t *debug_breakpoint, VALUE klass)
+{
+ return (klass != Qnil && rb_str_cmp(debug_breakpoint->source, rb_mod_name(klass)) == 0);
+}
+
+static int
+check_breakpoints(debug_context_t *debug_context, VALUE file, VALUE klass, VALUE pos)
+{
+ VALUE breakpoint;
+ debug_breakpoint_t *debug_breakpoint;
+ int i;
+
+ if(RARRAY(breakpoints)->len == 0)
+ return -1;
+ if(!debug_context->moved)
+ return -1;
+
+ for(i = 0; i < RARRAY(breakpoints)->len; i++)
+ {
+ breakpoint = rb_ary_entry(breakpoints, i);
+ Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
+ if(debug_breakpoint->pos != pos && !(TYPE(pos) == T_STRING &&
+ TYPE(debug_breakpoint->pos) == T_STRING && rb_str_cmp(debug_breakpoint->pos, pos) == 0))
+ continue;
+ if(filename_cmp(debug_breakpoint, file) || classname_cmp(debug_breakpoint, klass))
+ return i;
+ }
+ return -1;
+}
+
+/*
+ * This is a NASTY HACK. For some reasons rb_f_binding is decalred
+ * static in eval.c
+ */
+static VALUE
+create_binding(VALUE self)
+{
+ typedef VALUE (*bind_func_t)(VALUE);
+ static bind_func_t f_binding = NULL;
+
+ if(f_binding == NULL)
+ {
+ NODE *body, *method;
+ st_lookup(RCLASS(rb_mKernel)->m_tbl, idBinding, (st_data_t *)&body);
+ method = (NODE *)body->u2.value;
+ f_binding = (bind_func_t)method->u1.value;
+ }
+ return f_binding(self);
+}
+
+static void
+check_suspend(debug_context_t *debug_context)
+{
+ if(rb_thread_critical == Qtrue)
+ return;
+ while(1)
+ {
+ rb_thread_critical = Qtrue;
+ if(!debug_context->suspend)
+ break;
+ rb_ary_push(waiting, rb_thread_current());
+ debug_context->suspend = 0;
+ rb_thread_stop();
+ }
+ rb_thread_critical = Qfalse;
+}
+
+static VALUE
+get_breakpoint_at(int index)
+{
+ return rb_ary_entry(breakpoints, index);
+}
+
+static VALUE
+eval_expression(VALUE args)
+{
+ return rb_funcall2(rb_mKernel, idEval, 2, RARRAY(args)->ptr);
+}
+
+static int
+check_breakpoint_expression(VALUE breakpoint, VALUE binding)
+{
+ debug_breakpoint_t *debug_breakpoint;
+ VALUE args, expr_result;
+
+ Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
+ if(NIL_P(debug_breakpoint->expr))
+ return 1;
+
+ args = rb_ary_new3(2, debug_breakpoint->expr, binding);
+ expr_result = rb_protect(eval_expression, args, 0);
+ return RTEST(expr_result);
+}
+
+static void
+debug_event_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass)
+{
+ VALUE thread, context, binding, breakpoint;
+ debug_context_t *debug_context;
+ VALUE file = Qnil, line = Qnil;
+ int breakpoint_index = -1;
+
+ if (mid == ID_ALLOCATOR) return;
+ if(!node) return;
+ if (ruby_in_compile) return;
+
+ thread = rb_thread_current();
+ while(locker != Qnil && locker != thread)
+ {
+ add_to_locked(thread);
+ rb_thread_stop();
+ }
+
+ if(locker != Qnil) return;
+
+ locker = rb_thread_current();
+
+ context = thread_context_lookup(thread);
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ check_suspend(debug_context);
+
+ file = rb_str_new2(node->nd_file);
+ line = INT2FIX(nd_line(node));
+
+ if(did_moved())
+ debug_context->moved = 1;
+
+ switch(event)
+ {
+ case RUBY_EVENT_LINE:
+ {
+ set_frame_source(debug_context, file, line);
+
+ if(RTEST(tracing) || debug_context->tracing )
+ {
+ rb_funcall(context, idAtTracing, 2, file, line);
+ }
+
+ if(debug_context->dest_frame == -1 ||
+ RARRAY(debug_context->frames)->len == debug_context->dest_frame)
+ {
+ debug_context->stop_next--;
+ if(debug_context->stop_next < 0)
+ debug_context->stop_next = -1;
+ /* we check that we actualy moved to another line */
+ if(did_moved())
+ {
+ debug_context->stop_line--;
+ }
+ }
+ else if(RARRAY(debug_context->frames)->len < debug_context->dest_frame)
+ {
+ debug_context->stop_next = 0;
+ }
+
+ if(RARRAY(debug_context->frames)->len == 0)
+ {
+ save_call_frame(self, file, line, mid, debug_context);
+ }
+
+ if(debug_context->stop_next == 0 || debug_context->stop_line == 0 ||
+ (breakpoint_index = check_breakpoints(debug_context, file, klass, line)) != -1)
+ {
+ binding = self? create_binding(self) : Qnil;
+ /* check breakpoint expression */
+ if(breakpoint_index != -1)
+ {
+ breakpoint = get_breakpoint_at(breakpoint_index);
+ if(check_breakpoint_expression(breakpoint, binding))
+ {
+ rb_funcall(context, idAtBreakpoint, 1, breakpoint);
+ }
+ else
+ break;
+ }
+
+ /* reset all pointers */
+ debug_context->dest_frame = -1;
+ debug_context->stop_line = -1;
+ debug_context->stop_next = -1;
+
+ call_at_line(context, debug_context->thnum, binding, file, line);
+ }
+ break;
+ }
+ case RUBY_EVENT_C_CALL:
+ {
+ set_frame_source(debug_context, file, line);
+ break;
+ }
+ case RUBY_EVENT_CALL:
+ {
+ save_call_frame(self, file, line, mid, debug_context);
+ breakpoint_index = check_breakpoints(debug_context, file, klass, rb_str_new2(rb_id2name(mid)));
+ if(breakpoint_index != -1)
+ {
+ binding = self? create_binding(self) : Qnil;
+ breakpoint = get_breakpoint_at(breakpoint_index);
+ if(check_breakpoint_expression(breakpoint, binding))
+ {
+ rb_funcall(context, idAtBreakpoint, 1, breakpoint);
+ call_at_line(context, debug_context->thnum, binding, file, line);
+ }
+ }
+ break;
+ }
+ case RUBY_EVENT_RETURN:
+ case RUBY_EVENT_END:
+ {
+ if(RARRAY(debug_context->frames)->len == debug_context->stop_frame)
+ {
+ debug_context->stop_next = 1;
+ debug_context->stop_frame = 0;
+ }
+ rb_ary_shift(debug_context->frames);
+ break;
+ }
+ case RUBY_EVENT_CLASS:
+ {
+ save_call_frame(self, file, line, mid, debug_context);
+ break;
+ }
+ case RUBY_EVENT_RAISE:
+ {
+ VALUE ancestors;
+ VALUE expn_class, aclass;
+ int i;
+
+ expn_class = rb_obj_class(ruby_errinfo);
+ if( !NIL_P(rb_class_inherited_p(expn_class, rb_eSystemExit)) )
+ {
+ debug_stop(mDebugger);
+ break;
+ }
+
+ if(catchpoint == Qnil)
+ break;
+
+ ancestors = rb_mod_ancestors(expn_class);
+ for(i = 0; i < RARRAY(ancestors)->len; i++)
+ {
+ aclass = rb_ary_entry(ancestors, i);
+ if(rb_str_cmp(rb_mod_name(aclass), catchpoint) == 0)
+ {
+ rb_funcall(context, idAtCatchpoint, 1, ruby_errinfo);
+ binding = self? create_binding(self) : Qnil;
+ call_at_line(context, debug_context->thnum, binding, file, line);
+ break;
+ }
+ }
+
+ break;
+ }
+ case RUBY_EVENT_C_RETURN:
+ {
+ break;
+ }
+ }
+
+ locker = Qnil;
+ thread = remove_from_locked();
+ if(thread != Qnil)
+ rb_thread_run(thread);
+}
+
+static VALUE
+debug_stop_i(VALUE value)
+{
+ if(IS_STARTED)
+ debug_stop(value);
+ return Qnil;
+}
+
+/*
+ * call-seq:
+ * Debugger.start -> bool
+ * Debugger.start { ... } -> obj
+ *
+ * This method activates the debugger.
+ * If it's called without a block it returns +true+, unless debugger was already started.
+ * If a block is given, it starts debugger and yields to block. At the end of stops the debugger
+ * with Debugger.stop method.
+ *
+ * <i>Note that if you want to stop debugger, you must call Debugger.stop as many time as you
+ * called Debugger.start method.</i>
+ */
+static VALUE
+debug_start(VALUE self)
+{
+ start_count++;
+
+ if(IS_STARTED)
+ return Qfalse;
+
+ threads_tbl = rb_hash_new();
+ breakpoints = rb_ary_new();
+ waiting = rb_ary_new();
+ locker = Qnil;
+
+ rb_add_event_hook(debug_event_hook,
+ RUBY_EVENT_LINE | RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN |
+ RUBY_EVENT_CALL | RUBY_EVENT_RETURN | RUBY_EVENT_CLASS |
+ RUBY_EVENT_END | RUBY_EVENT_RAISE
+ );
+
+ if(rb_block_given_p())
+ return rb_ensure(rb_yield, Qnil, debug_stop_i, Qnil);
+
+ return Qtrue;
+}
+
+/*
+ * call-seq:
+ * Debugger.stop -> bool
+ *
+ * This method disacivates the debugger. It returns +true+ if the debugger is disacivated,
+ * otherwise it returns +false+.
+ *
+ * <i>Note that if you want to stop debugger, you must call Debugger.stop as many time as you
+ * called Debugger.start method.</i>
+ */
+static VALUE
+debug_stop(VALUE self)
+{
+ debug_check_started();
+
+ start_count--;
+ if(start_count)
+ return Qfalse;
+
+ rb_remove_event_hook(debug_event_hook);
+
+ waiting = Qnil;
+ locker = Qnil;
+ breakpoints = Qnil;
+ threads_tbl = Qnil;
+
+ return Qtrue;
+}
+
+static void
+breakpoint_mark(void *data)
+{
+ debug_breakpoint_t *breakpoint;
+ breakpoint = (debug_breakpoint_t *)data;
+ rb_gc_mark(breakpoint->source);
+ rb_gc_mark(breakpoint->pos);
+ rb_gc_mark(breakpoint->expr);
+}
+
+/*
+ * call-seq:
+ * Debugger.add_breakpoint(source, pos, condition = nil) -> breakpoint
+ *
+ * Adds a new breakpoint.
+ * <i>source</i> is a name of a file or a class.
+ * <i>pos</i> is a line number or a method name if <i>source</i> is a class name.
+ * <i>condition</i> is a string which is evaluated to +true+ when this breakpoint
+ * is activated.
+ */
+static VALUE
+debug_add_breakpoint(int argc, VALUE *argv, VALUE self)
+{
+ VALUE source, pos, expr;
+ VALUE result;
+ debug_breakpoint_t *breakpoint;
+
+ debug_check_started();
+
+ if(rb_scan_args(argc, argv, "21", &source, &pos, &expr) == 2)
+ {
+ expr = Qnil;
+ }
+
+ breakpoint = ALLOC(debug_breakpoint_t);
+ breakpoint->source = StringValue(source);
+ breakpoint->pos = pos;
+ breakpoint->expr = NIL_P(expr) ? expr: StringValue(expr);
+ result = Data_Wrap_Struct(cBreakpoint, breakpoint_mark, xfree, breakpoint);
+ rb_ary_push(breakpoints, result);
+ return result;
+}
+
+/*
+ * call-seq:
+ * Debugger.breakpoints -> array
+ *
+ * Returns an array of breakpoints.
+ */
+static VALUE
+debug_breakpoints(VALUE self)
+{
+ debug_check_started();
+
+ return breakpoints;
+}
+
+/*
+ * call-seq:
+ * Debugger.checkpoint -> string
+ *
+ * Returns a current checkpoint, which is a name of exception that will
+ * trigger a debugger when raised.
+ */
+static VALUE
+debug_catchpoint(VALUE self)
+{
+ debug_check_started();
+
+ return catchpoint;
+}
+
+/*
+ * call-seq:
+ * Debugger.checkpoint = string -> string
+ *
+ * Sets checkpoint.
+ */
+static VALUE
+debug_set_catchpoint(VALUE self, VALUE value)
+{
+ debug_check_started();
+
+ if (!NIL_P(value) && TYPE(value) != T_STRING) {
+ rb_raise(rb_eTypeError, "value of checkpoint must be String");
+ }
+ if(NIL_P(value))
+ catchpoint = Qnil;
+ else
+ catchpoint = rb_str_dup(value);
+ return value;
+}
+
+static int
+find_last_context_func(VALUE key, VALUE value, VALUE *result)
+{
+ debug_context_t *debug_context;
+ Data_Get_Struct(value, debug_context_t, debug_context);
+ if(debug_context->thnum == last_debugged_thnum)
+ {
+ *result = value;
+ return ST_STOP;
+ }
+ return ST_CONTINUE;
+}
+
+/*
+ * call-seq:
+ * Debugger.last_interrupted -> context
+ *
+ * Returns last debugged context.
+ */
+static VALUE
+debug_last_interrupted(VALUE self)
+{
+ VALUE result = Qnil;
+
+ debug_check_started();
+
+ rb_hash_foreach(threads_tbl, find_last_context_func, (st_data_t)&result);
+ return result;
+}
+
+/*
+ * call-seq:
+ * Debugger.current_context -> context
+ *
+ * Returns current context.
+ * <i>Note:</i> Debugger.current_context.thread == Thread.current
+ */
+static VALUE
+debug_current_context(VALUE self)
+{
+ VALUE thread, context;
+
+ debug_check_started();
+
+ thread = rb_thread_current();
+ context = thread_context_lookup(thread);
+
+ return context;
+}
+
+/*
+ * call-seq:
+ * Debugger.contexts -> array
+ *
+ * Returns an array of all contexts.
+ */
+static VALUE
+debug_contexts(VALUE self)
+{
+ volatile VALUE list;
+ volatile VALUE new_list;
+ VALUE thread, context;
+ debug_context_t *debug_context;
+ int i;
+
+ debug_check_started();
+
+ new_list = rb_ary_new();
+ list = rb_funcall(rb_cThread, idList, 0);
+ for(i = 0; i < RARRAY(list)->len; i++)
+ {
+ thread = rb_ary_entry(list, i);
+ context = thread_context_lookup(thread);
+ rb_ary_push(new_list, context);
+ }
+ /*
+ * I wonder why rb_hash_clear is declared static?
+ */
+ rb_funcall(threads_tbl, idClear, 0);
+ for(i = 0; i < RARRAY(new_list)->len; i++)
+ {
+ context = rb_ary_entry(new_list, i);
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ rb_hash_aset(threads_tbl, debug_context->thread, context);
+ }
+
+ return new_list;
+}
+
+/*
+ * call-seq:
+ * Debugger.suspend -> Debugger
+ *
+ * Suspends all contexts.
+ */
+static VALUE
+debug_suspend(VALUE self)
+{
+ VALUE current, context;
+ VALUE saved_crit;
+ VALUE context_list;
+ debug_context_t *debug_context;
+ int i;
+
+ debug_check_started();
+
+ saved_crit = rb_thread_critical;
+ rb_thread_critical = Qtrue;
+ context_list = debug_contexts(self);
+ current = thread_context_lookup(rb_thread_current());
+
+ for(i = 0; i < RARRAY(context_list)->len; i++)
+ {
+ context = rb_ary_entry(context_list, i);
+ if(current == context)
+ continue;
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ debug_context->suspend = 1;
+ }
+ rb_thread_critical = saved_crit;
+
+ if(rb_thread_critical == Qfalse)
+ rb_thread_schedule();
+
+ return self;
+}
+
+/*
+ * call-seq:
+ * Debugger.resume -> Debugger
+ *
+ * Resumes all contexts.
+ */
+static VALUE
+debug_resume(VALUE self)
+{
+ VALUE current, context;
+ VALUE saved_crit;
+ VALUE thread;
+ VALUE context_list;
+ debug_context_t *debug_context;
+ int i;
+
+ debug_check_started();
+
+ saved_crit = rb_thread_critical;
+ rb_thread_critical = Qtrue;
+ context_list = debug_contexts(self);
+
+ current = thread_context_lookup(rb_thread_current());
+ for(i = 0; i < RARRAY(context_list)->len; i++)
+ {
+ context = rb_ary_entry(context_list, i);
+ if(current == context)
+ continue;
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ debug_context->suspend = 0;
+ }
+ for(i = 0; i < RARRAY(waiting)->len; i++)
+ {
+ thread = rb_ary_entry(waiting, i);
+ rb_thread_run(thread);
+ }
+ rb_ary_clear(waiting);
+ rb_thread_critical = saved_crit;
+
+ rb_thread_schedule();
+
+ return self;
+}
+
+/*
+ * call-seq:
+ * Debugger.tracing -> bool
+ *
+ * Returns +true+ is a global tracing is activated.
+ */
+static VALUE
+debug_tracing(VALUE self)
+{
+ return tracing;
+}
+
+/*
+ * call-seq:
+ * Debugger.tracing = bool
+ *
+ * Sets a global tracing flag.
+ */
+static VALUE
+debug_set_tracing(VALUE self, VALUE value)
+{
+ tracing = RTEST(value) ? Qtrue : Qfalse;
+ return value;
+}
+
+/*
+ * call-seq:
+ * Debugger.debug_load(file) -> nil
+ *
+ * Same as Kernel#load but resets current context's frames.
+ * Used from <tt>rdebug</tt> script only.
+ */
+static VALUE
+debug_debug_load(VALUE self, VALUE file)
+{
+ VALUE context;
+ debug_context_t *debug_context;
+
+ debug_start(self);
+
+ context = debug_current_context(self);
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ rb_ary_clear(debug_context->frames);
+ rb_load(file, 0);
+
+ debug_stop(self);
+ return Qnil;
+}
+
+/*
+ * call-seq:
+ * context.stop_next = steps
+ *
+ * Stops the current context after a number +steps+ are made.
+ */
+static VALUE
+context_stop_next(VALUE self, VALUE steps)
+{
+ debug_context_t *debug_context;
+
+ debug_check_started();
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ if(FIX2INT(steps) < 0)
+ rb_raise(rb_eRuntimeError, "Steps argument can't be negative.");
+ debug_context->stop_next = FIX2INT(steps);
+
+ return steps;
+}
+
+/*
+ * call-seq:
+ * context.step_over(steps)
+ *
+ * Steps over a +steps+ number of times.
+ */
+static VALUE
+context_step_over(int argc, VALUE *argv, VALUE self)
+{
+ VALUE lines, frame;
+ debug_context_t *debug_context;
+
+ debug_check_started();
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ if(RARRAY(debug_context->frames)->len == 0)
+ rb_raise(rb_eRuntimeError, "No frames collected.");
+
+ rb_scan_args(argc, argv, "11", &lines, &frame);
+ debug_context->stop_line = FIX2INT(lines);
+ if(argc == 1)
+ {
+ debug_context->dest_frame = RARRAY(debug_context->frames)->len;
+ }
+ else
+ {
+ if(FIX2INT(frame) < 0 && FIX2INT(frame) >= RARRAY(debug_context->frames)->len)
+ rb_raise(rb_eRuntimeError, "Destination frame is out of range.");
+ debug_context->dest_frame = FIX2INT(frame);
+ }
+
+ return Qnil;
+}
+
+/*
+ * call-seq:
+ * context.stop_frame(frame)
+ *
+ * Stops when a frame with number +frame+ is activated. Implements +up+ and +down+ commands.
+ */
+static VALUE
+context_stop_frame(VALUE self, VALUE frame)
+{
+ debug_context_t *debug_context;
+
+ debug_check_started();
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ if(FIX2INT(frame) < 0 && FIX2INT(frame) >= RARRAY(debug_context->frames)->len)
+ rb_raise(rb_eRuntimeError, "Stop frame is out of range.");
+ debug_context->stop_frame = FIX2INT(frame);
+
+ return frame;
+}
+
+/*
+ * call-seq:
+ * context.frames -> array
+ *
+ * Returns an array of frames.
+ */
+static VALUE
+context_frames(VALUE self)
+{
+ debug_context_t *debug_context;
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ return debug_context->frames;
+}
+
+/*
+ * call-seq:
+ * context.thread -> trhread
+ *
+ * Returns a thread this context is associated with.
+ */
+static VALUE
+context_thread(VALUE self)
+{
+ debug_context_t *debug_context;
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ return debug_context->thread;
+}
+
+/*
+ * call-seq:
+ * context.thnum -> int
+ *
+ * Returns the context's number.
+ */
+static VALUE
+context_thnum(VALUE self)
+{
+ debug_context_t *debug_context;
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ return INT2FIX(debug_context->thnum);
+}
+
+/*
+ * call-seq:
+ * context.set_suspend -> nil
+ *
+ * Suspends the thread when it is running.
+ */
+static VALUE
+context_set_suspend(VALUE self)
+{
+ debug_context_t *debug_context;
+
+ debug_check_started();
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ debug_context->suspend = 1;
+ return Qnil;
+}
+
+/*
+ * call-seq:
+ * context.clear_suspend -> nil
+ *
+ * Clears a suspend flag.
+ */
+static VALUE
+context_clear_suspend(VALUE self)
+{
+ debug_context_t *debug_context;
+
+ debug_check_started();
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ debug_context->suspend = 0;
+ return Qnil;
+}
+
+/*
+ * call-seq:
+ * context.tracing -> bool
+ *
+ * Returns the tracing flag for the current context.
+ */
+static VALUE
+context_tracing(VALUE self)
+{
+ debug_context_t *debug_context;
+
+ debug_check_started();
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ return debug_context->tracing ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ * context.tracking = bool
+ *
+ * Controls the tracing for this context.
+ */
+static VALUE
+context_set_tracing(VALUE self, VALUE value)
+{
+ debug_context_t *debug_context;
+
+ debug_check_started();
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ debug_context->tracing = RTEST(value) ? 1 : 0;
+ return value;
+}
+
+/*
+ * call-seq:
+ * frame.file -> string
+ *
+ * Returns the name of the file.
+ */
+static VALUE
+frame_file(VALUE self)
+{
+ debug_frame_t *debug_frame;
+
+ Data_Get_Struct(self, debug_frame_t, debug_frame);
+ return debug_frame->file;
+}
+
+/*
+ * call-seq:
+ * frame.line -> int
+ *
+ * Returns the line number in the file.
+ */
+static VALUE
+frame_line(VALUE self)
+{
+ debug_frame_t *debug_frame;
+
+ Data_Get_Struct(self, debug_frame_t, debug_frame);
+ return debug_frame->line;
+}
+
+/*
+ * call-seq:
+ * frame.binding -> binding
+ *
+ * Returns the binding captured at the moment this frame was created.
+ */
+static VALUE
+frame_binding(VALUE self)
+{
+ debug_frame_t *debug_frame;
+
+ Data_Get_Struct(self, debug_frame_t, debug_frame);
+ return debug_frame->binding;
+}
+
+/*
+ * call-seq:
+ * frame.id -> sym
+ *
+ * Returns the sym of the called method.
+ */
+static VALUE
+frame_id(VALUE self)
+{
+ debug_frame_t *debug_frame;
+
+ Data_Get_Struct(self, debug_frame_t, debug_frame);
+ return debug_frame->id ? ID2SYM(debug_frame->id): Qnil;
+}
+
+/*
+ * call-seq:
+ * breakpoint.source -> string
+ *
+ * Returns a source of the breakpoint.
+ */
+static VALUE
+breakpoint_source(VALUE self)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ return breakpoint->source;
+}
+
+/*
+ * call-seq:
+ * breakpoint.pos -> string or int
+ *
+ * Returns a position of this breakpoint.
+ */
+static VALUE
+breakpoint_pos(VALUE self)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ return breakpoint->pos;
+}
+
+/*
+ * call-seq:
+ * breakpoint.expr -> string
+ *
+ * Returns a codition expression when this breakpoint should be activated.
+ */
+static VALUE
+breakpoint_expr(VALUE self)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ return breakpoint->expr;
+}
+
+/*
+ * Document-class: Context
+ *
+ * == Summary
+ *
+ * Debugger keeps a single instance of this class for each Ruby thread.
+ * This class provides access to stack frames (see Frame class). Also it
+ * gives you ability to step thought the code.
+ */
+static void
+Init_context()
+{
+ cContext = rb_define_class_under(mDebugger, "Context", rb_cObject);
+ rb_define_method(cContext, "stop_next=", context_stop_next, 1);
+ rb_define_method(cContext, "step_over", context_step_over, -1);
+ rb_define_method(cContext, "stop_frame=", context_stop_frame, 1);
+ rb_define_method(cContext, "frames", context_frames, 0);
+ rb_define_method(cContext, "thread", context_thread, 0);
+ rb_define_method(cContext, "thnum", context_thnum, 0);
+ rb_define_method(cContext, "set_suspend", context_set_suspend, 0);
+ rb_define_method(cContext, "clear_suspend", context_clear_suspend, 0);
+ rb_define_method(cContext, "tracing", context_tracing, 0);
+ rb_define_method(cContext, "tracing=", context_set_tracing, 1);
+}
+
+/*
+ * Document-class: Frame
+ *
+ * == Summary
+ *
+ * This class holds infomation about a particular call frame.
+ */
+static void
+Init_frame()
+{
+ cFrame = rb_define_class_under(cContext, "Frame", rb_cObject);
+ rb_define_method(cFrame, "file", frame_file, 0);
+ rb_define_method(cFrame, "line", frame_line, 0);
+ rb_define_method(cFrame, "binding", frame_binding, 0);
+ rb_define_method(cFrame, "id", frame_id, 0);
+}
+
+/*
+ * Document-class: Breakpoint
+ *
+ * == Summary
+ *
+ * This class represents a breakpoint. It defines position of the breakpoint and
+ * condition when this breakpoint should be triggered.
+ */
+static void
+Init_breakpoint()
+{
+ cBreakpoint = rb_define_class_under(mDebugger, "Breakpoint", rb_cObject);
+ rb_define_method(cBreakpoint, "source", breakpoint_source, 0);
+ rb_define_method(cBreakpoint, "pos", breakpoint_pos, 0);
+ rb_define_method(cBreakpoint, "expr", breakpoint_expr, 0);
+}
+
+/*
+ * Document-class: Debugger
+ *
+ * == Summary
+ *
+ * This is a singleton class allows controlling the debugger. Use it to start/stop debugger,
+ * set/remove breakpoints, etc.
+ */
+#if defined(_WIN32)
+__declspec(dllexport)
+#endif
+void
+Init_ruby_debug()
+{
+ mDebugger = rb_define_module("Debugger");
+ rb_define_const(mDebugger, "VERSION", rb_str_new2(DEBUG_VERSION));
+ rb_define_module_function(mDebugger, "start", debug_start, 0);
+ rb_define_module_function(mDebugger, "stop", debug_stop, 0);
+ rb_define_module_function(mDebugger, "started?", debug_is_started, 0);
+ rb_define_module_function(mDebugger, "breakpoints", debug_breakpoints, 0);
+ rb_define_module_function(mDebugger, "add_breakpoint", debug_add_breakpoint, -1);
+ rb_define_module_function(mDebugger, "catchpoint", debug_catchpoint, 0);
+ rb_define_module_function(mDebugger, "catchpoint=", debug_set_catchpoint, 1);
+ rb_define_module_function(mDebugger, "last_context", debug_last_interrupted, 0);
+ rb_define_module_function(mDebugger, "contexts", debug_contexts, 0);
+ rb_define_module_function(mDebugger, "current_context", debug_current_context, 0);
+ rb_define_module_function(mDebugger, "suspend", debug_suspend, 0);
+ rb_define_module_function(mDebugger, "resume", debug_resume, 0);
+ rb_define_module_function(mDebugger, "tracing", debug_tracing, 0);
+ rb_define_module_function(mDebugger, "tracing=", debug_set_tracing, 1);
+ rb_define_module_function(mDebugger, "debug_load", debug_debug_load, 1);
+
+ Init_context();
+ Init_frame();
+ Init_breakpoint();
+
+ idAtLine = rb_intern("at_line");
+ idAtBreakpoint = rb_intern("at_breakpoint");
+ idAtCatchpoint = rb_intern("at_catchpoint");
+ idAtTracing = rb_intern("at_tracing");
+ idBinding = rb_intern("binding");
+ idBasename = rb_intern("basename");
+ idEval = rb_intern("eval");
+ idList = rb_intern("list");
+ idClear = rb_intern("clear");
+ idIndex = rb_intern("index");
+
+ file_separator = rb_eval_string("File::SEPARATOR");
+ alt_file_separator = rb_eval_string("File::ALT_SEPARATOR");
+
+ rb_global_variable(&threads_tbl);
+ rb_global_variable(&breakpoints);
+ rb_global_variable(&catchpoint);
+ rb_global_variable(&waiting);
+ rb_global_variable(&locker);
+ rb_global_variable(&file_separator);
+ rb_global_variable(&alt_file_separator);
+}
BIN ext/win32/ruby_debug.so
Binary file not shown.
233 lib/ruby-debug.rb
@@ -0,0 +1,233 @@
+require 'pp'
+require 'stringio'
+require 'thread'
+require 'ruby_debug.so'
+require 'ruby-debug/lock'
+require 'ruby-debug/processor'
+
+SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
+
+module Debugger
+ MUTEX = Lock.new
+ PORT = 8989
+
+ @processor = CommandProcessor.new
+
+ class Context
+ def interrupt
+ self.stop_next = 1
+ end
+
+ private
+
+ def processor
+ Debugger.processor
+ end
+
+ def at_breakpoint(breakpoint)
+ processor.at_breakpoint(self, breakpoint)
+ end
+
+ def at_catchpoint(excpt)
+ processor.at_catchpoint(self, excpt)
+ end
+
+ def at_tracing(file, line)
+ processor.at_tracing(self, file, line)
+ end
+
+ def at_line(file, line, binding)
+ MUTEX.lock
+ processor.at_line(self, file, line, binding)
+ MUTEX.unlock
+ Debugger.resume
+ end
+ end
+
+ class << self
+ attr_accessor :processor
+
+ # stop main thread when remote connection established
+ attr_accessor :stop_on_connect
+
+ # in remote mode, wait for the remote connection
+ attr_accessor :wait_connection
+
+ attr_reader :thread, :control_thread
+
+ #
+ # Interrupts the main thread
+ #
+ def interrupt
+ context = contexts.find{|c| c.thread == Thread.current }
+ context.interrupt
+ end
+
+ #
+ # Interrupts the last debugged thread
+ #
+ def interrupt_last
+ if context = last_context
+ return nil unless context.thread.alive?
+ context.interrupt
+ end
+ context
+ end
+
+ def interface=(value) # :nodoc:
+ processor.interface = value
+ end
+
+ #
+ # Starts a remote debugger.
+ #
+ def start_remote(host = nil, port = PORT)
+ return if @thread
+ return if started?
+
+ self.interface = nil
+ start
+
+ require "socket"
+
+ if port.kind_of?(Array)
+ cmd_port, ctrl_port = port
+ else
+ cmd_port, ctrl_port = port, port + 1
+ end
+
+ @control_thread = Thread.start do
+ server = TCPServer.new(host, ctrl_port)
+ while (session = server.accept)
+ interface = RemoteInterface.new(session)
+ processor = ControlCommandProcessor.new(interface)
+ processor.process_commands
+ end
+ end
+
+ mutex = Mutex.new
+ proceed = ConditionVariable.new
+
+ @thread = Thread.start do
+ server = TCPServer.new(host, cmd_port)
+ while (session = server.accept)
+ self.interface = RemoteInterface.new(session)
+ if wait_connection
+ mutex.synchronize do
+ proceed.signal
+ end
+ else
+ stop_main_thread
+ end
+ end
+ end
+ if wait_connection
+ mutex.synchronize do
+ proceed.wait(mutex)
+ end
+ stop_main_thread
+ end
+ end
+ alias start_server start_remote
+
+ #
+ # Connects to the remote debugger
+ #
+ def start_client(host = 'localhost', port = PORT)
+ require "socket"
+ interface = Debugger::LocalInterface.new
+ socket = TCPSocket.new(host, port)
+ puts "Connected."
+
+ catch(:exit) do
+ while (line = socket.gets)
+ case line
+ when /^PROMPT (.*)$/
+ input = interface.read_command($1)
+ throw :exit unless input
+ socket.puts input
+ when /^CONFIRM (.*)$/
+ input = interface.confirm($1)
+ throw :exit unless input
+ socket.puts input
+ else
+ print line
+ end
+ end
+ end
+ socket.close
+ puts
+ end
+
+ def stop_main_thread # :nodoc:
+ return unless stop_on_connect
+
+ context = contexts.find{ |c| c.thread == Thread.main }
+ context.stop_next = 2
+ end
+ private :stop_main_thread
+
+ def source_for(file) # :nodoc:
+ if source = SCRIPT_LINES__[file]
+ return source unless source == true
+ end
+ if File.exists?(file)
+ SCRIPT_LINES__[file] = File.readlines(file)
+ end
+ end
+
+ def line_at(file, line) # :nodoc:
+ lines = source_for(file)
+ if lines
+ line = lines[line-1]
+ return "\n" unless line
+ return "#{line.gsub(/^\s+/, '').chomp}\n"
+ end
+ return "\n"
+ end
+
+ #
+ # Runs a script file
+ #
+ def run_script(file, out = processor.interface)
+ interface = ScriptInterface.new(file, out)
+ processor = ControlCommandProcessor.new(interface)
+ processor.process_commands
+ end
+ end
+end
+
+module Kernel
+ #
+ # Stops the current thread after a number of _steps_ made.
+ #
+ def debugger(steps = 1)
+ Debugger.current_context.stop_next = steps
+ end
+
+ #
+ # Returns a binding of n-th call frame
+ #
+ def binding_n(n = 0)
+ frame = Debugger.current_context.frames[n+1]
+ raise "Unknown frame #{n}" unless frame
+ frame.binding
+ end
+end
+
+class Module
+ #
+ # Wraps the +meth+ method with Debugger.start {...} block.
+ #
+ def debug_method(meth)
+ alias_method "__debugee_#{meth}".to_sym, meth
+ class_eval <<-EOD
+ def #{meth}(*args, &block)
+ Debugger.start do
+ debugger 2
+ __debugee_#{meth}(*args, &block)
+ end
+ end
+ EOD
+ end
+end
95 lib/ruby-debug/command.rb
@@ -0,0 +1,95 @@
+module Debugger
+ class Command # :nodoc:
+ class << self
+ def commands
+ @commands ||= []
+ end
+
+ DEF_OPTIONS = {
+ :event => true,
+ :control => false,
+ :always_run => false,
+ :unknown => false,
+ }
+
+ def inherited(klass)
+ DEF_OPTIONS.each do |o, v|
+ klass.options[o] = v if klass.options[o].nil?
+ end
+ commands << klass
+ end
+
+ def load_commands
+ dir = File.dirname(__FILE__)
+ Dir[File.join(dir, 'commands', '*')].each do |file|
+ require file
+ end
+ end
+
+ def method_missing(meth, *args, &block)
+ if meth.to_s =~ /^(.+?)=$/
+ @options[$1.intern] = args.first
+ else
+ if @options.has_key?(meth)
+ @options[meth]
+ else
+ super
+ end
+ end
+ end
+
+ def options
+ @options ||= {}
+ end
+ end
+
+ def initialize(state)
+ @state = state
+ end
+
+ def match(input)
+ @match = regexp.match(input)
+ end
+
+ protected
+
+ def print(*args)
+ @state.print(*args)
+ end
+
+ def confirm(msg)
+ @state.confirm(msg) == 'y'
+ end
+
+ def debug_eval(str)
+ begin
+ val = eval(str, @state.binding)
+ rescue StandardError, ScriptError => e
+ at = eval("caller(1)", @state.binding)
+ print "%s:%s\n", at.shift, e.to_s.sub(/\(eval\):1:(in `.*?':)?/, '')
+ for i in at
+ print "\tfrom %s\n", i
+ end
+ throw :debug_error
+ end
+ end
+
+ def debug_silent_eval(str)
+ begin
+ eval(str, @state.binding)
+ rescue StandardError, ScriptError
+ nil
+ end
+ end
+
+ def line_at(file, line)
+ Debugger.line_at(file, line)
+ end
+
+ def get_context(thnum)
+ Debugger.contexts.find{|c| c.thnum == thnum}
+ end
+ end
+
+ Command.load_commands
+end
136 lib/ruby-debug/commands/breakpoints.rb
@@ -0,0 +1,136 @@
+module Debugger
+ class AddBreakpoint < Command # :nodoc:
+ self.control = true
+
+ def regexp
+ / ^\s*
+ b(?:reak)?
+ \s+
+ (?:
+ (\d+) |
+ (.+?)[:.#]([^.:\s]+)
+ )
+ (?:\s+
+ if\s+(.+)
+ )?
+ $
+ /x
+ end
+
+ def execute
+ if @match[1]
+ pos, _, _, expr = @match.captures
+ else
+ _, file, pos, expr = @match.captures
+ end
+
+ if file.nil?
+ file = File.basename(@state.file)
+ else
+ if pos !~ /^\d+$/
+ klass = debug_silent_eval(file)
+ if klass && !klass.kind_of?(Module)
+ print "Unknown class #{file}\n"
+ throw :debug_error
+ end
+ file = klass.name if klass
+ else
+ file = File.expand_path(file) if file.index(File::SEPARATOR) || \
+ File::ALT_SEPARATOR && file.index(File::ALT_SEPARATOR)
+ end
+ end
+
+ if pos =~ /^\d+$/
+ pos = pos.to_i
+ else
+ pos = pos.intern.id2name
+ end
+
+ Debugger.add_breakpoint file, pos, expr
+ print "Set breakpoint %d at %s:%s\n", Debugger.breakpoints.size, file, pos.to_s
+ end
+
+ class << self
+ def help_command
+ 'break'
+ end
+
+ def help(cmd)
+ %{
+ b[reak] [file|class(:|.|#)]<line|method> [if expr] -
+ \tset breakpoint to some position, (optionally) if expr == true
+ }
+ end
+ end
+ end
+
+ class BreakpointsCommand < Command # :nodoc:
+ self.control = true
+
+ def regexp
+ /^\s*b(?:reak)?$/
+ end
+
+ def execute
+ unless Debugger.breakpoints.empty?
+ print "Breakpoints:\n"
+ Debugger.breakpoints.each_with_index do |b, n|
+ if b.expr.nil?
+ print " %d %s:%s\n", n+1, b.source, b.pos
+ else
+ print " %d %s:%s if %s\n", n+1, b.source, b.pos, b.expr
+ end
+ end
+ print "\n"
+ else
+ print "No breakpoints\n"
+ end
+ end
+
+ class << self
+ def help_command
+ 'break'
+ end
+
+ def help(cmd)
+ %{
+ b[reak]\tlist breakpoints
+ }
+ end
+ end
+ end
+
+ class DeleteBreakpointCommand < Command # :nodoc:
+ self.control = true
+