├── .gitignore
├── .idea
└── vcs.xml
├── .travis.yml
├── Gemfile
├── LICENSE
├── MIT_LICENSE
├── README.md
├── Rakefile
├── debase.gemspec
├── ext
├── attach
│ ├── attach.c
│ ├── attach.h
│ └── extconf.rb
├── breakpoint.c
├── context.c
├── debase_internals.c
├── debase_internals.h
├── extconf.rb
├── hacks.h
└── locker.c
├── lib
├── debase.rb
└── debase
│ ├── context.rb
│ ├── rbx.rb
│ ├── rbx
│ ├── breakpoint.rb
│ ├── context.rb
│ ├── frame.rb
│ └── monkey.rb
│ └── version.rb
└── test
├── example
├── a
│ └── example.rb
├── at-exit.rb
├── b
│ └── example.rb
├── bootsnap
│ ├── a.rb
│ └── bootsnap.rb
├── bp_loop_issue.rb
├── breakpoints-basename.rb
├── brkpt-class-bug.rb
├── classes.rb
├── dollar-0.rb
├── except-bug1.rb
├── file with space.rb
├── gcd.rb
├── info-var-bug.rb
├── info-var-bug2.rb
├── null.rb
├── output.rb
├── pm-bug.rb
├── pm.rb
└── raise.rb
├── helper.rb
├── test_base.rb
├── test_breakpoints.rb
├── test_catchpoint.rb
├── test_load.rb
└── test_reload_bug.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | .bundle
3 | Gemfile.lock
4 | pkg/*
5 | ext/**/*.o
6 | ext/**/*.bundle
7 | ext/**/Makefile
8 | ext/**/*.log
9 | ext/**/*.so
10 | ext/**/*.def
11 | .ruby-gemset
12 | .ruby-version
13 | .idea/*
14 | !.idea/vcs.xml
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | matrix:
3 | fast_finish: true
4 | include:
5 | - os: linux
6 | dist: xenial
7 | rvm: 3.4-preview2
8 | - os: linux
9 | dist: xenial
10 | rvm: 3.3
11 | - os: linux
12 | dist: xenial
13 | rvm: 3.2
14 | - os: linux
15 | dist: xenial
16 | rvm: 3.1
17 | - os: linux
18 | dist: xenial
19 | rvm: 3.0
20 | - os: linux
21 | dist: xenial
22 | rvm: 2.7
23 | - os: linux
24 | dist: xenial
25 | rvm: 2.6
26 | - os: linux
27 | dist: xenial
28 | rvm: 2.5
29 | - os: linux
30 | dist: xenial
31 | rvm: 2.4
32 | - os: linux
33 | dist: xenial
34 | rvm: 2.3
35 | - os: linux
36 | dist: xenial
37 | rvm: 2.2
38 | - os: linux
39 | dist: xenial
40 | rvm: 2.1
41 | - os: linux
42 | dist: bionic
43 | rvm: 3.4-preview2
44 | - os: linux
45 | dist: bionic
46 | rvm: 3.3
47 | - os: linux
48 | dist: bionic
49 | rvm: 3.2
50 | - os: linux
51 | dist: bionic
52 | rvm: 3.1
53 | - os: linux
54 | dist: bionic
55 | rvm: 3.0
56 | - os: linux
57 | dist: bionic
58 | rvm: 2.7
59 | - os: linux
60 | dist: bionic
61 | rvm: 2.6
62 | - os: linux
63 | dist: bionic
64 | rvm: 2.5
65 | - os: linux
66 | dist: bionic
67 | rvm: 2.4
68 | - os: linux
69 | dist: bionic
70 | rvm: 2.3
71 | - os: linux
72 | dist: focal
73 | rvm: 3.4-preview2
74 | - os: linux
75 | dist: focal
76 | rvm: 3.3
77 | - os: linux
78 | dist: focal
79 | rvm: 3.2
80 | - os: linux
81 | dist: focal
82 | rvm: 3.1
83 | - os: linux
84 | dist: focal
85 | rvm: 3.0
86 | - os: linux
87 | dist: focal
88 | rvm: 2.7
89 | - os: linux
90 | dist: focal
91 | rvm: 2.6
92 | - os: linux
93 | dist: focal
94 | rvm: 2.5
95 | - os: linux
96 | dist: focal
97 | rvm: 2.4
98 | - os: osx
99 | osx_image: xcode11.3
100 | rvm: 3.4-preview2
101 | - os: osx
102 | osx_image: xcode11.3
103 | rvm: 3.3
104 | - os: osx
105 | osx_image: xcode11.3
106 | rvm: 3.2
107 | - os: osx
108 | osx_image: xcode11.3
109 | rvm: 3.1
110 | - os: osx
111 | osx_image: xcode11.3
112 | rvm: 3.0
113 | - os: osx
114 | osx_image: xcode11.3
115 | rvm: 2.7
116 | - os: osx
117 | osx_image: xcode11.3
118 | rvm: 2.6
119 | - os: osx
120 | osx_image: xcode11.3
121 | rvm: 2.5
122 | - os: osx
123 | osx_image: xcode11.3
124 | rvm: 2.4
125 | - os: osx
126 | osx_image: xcode12.2
127 | rvm: 3.4-preview2
128 | - os: osx
129 | osx_image: xcode12.2
130 | rvm: 3.3
131 | - os: osx
132 | osx_image: xcode12.2
133 | rvm: 3.2
134 | - os: osx
135 | osx_image: xcode12.2
136 | rvm: 3.1
137 | - os: osx
138 | osx_image: xcode12.2
139 | rvm: 3.0
140 | - os: osx
141 | osx_image: xcode12.2
142 | rvm: 2.7
143 | - os: osx
144 | osx_image: xcode12.2
145 | rvm: 2.6
146 | - os: osx
147 | osx_image: xcode12.2
148 | rvm: 2.5
149 | - os: osx
150 | osx_image: xcode12.2
151 | rvm: 2.4
152 | - os: osx
153 | osx_image: xcode12.2
154 | rvm: ruby-head
155 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | # Specify your gem's dependencies in debase.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2005 Kent Sibilev
2 | All rights reserved.
3 | *
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 | 1. Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright
10 | notice, this list of conditions and the following disclaimer in the
11 | documentation and/or other materials provided with the distribution.
12 | *
13 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 | SUCH DAMAGE.
--------------------------------------------------------------------------------
/MIT_LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2007-2008, debug-commons team
2 | Copyright (c) 2012, Dennis Ushakov
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://rubygems.org/gems/debase)
2 | [](https://travis-ci.org/ruby-debug/debase)
3 |
4 | ## Overview
5 |
6 | debase is a fast implementation of the standard debugger debug.rb for Ruby 2.0.0. The faster execution speed and 2.0.0
7 | compatibility is achieved by utilizing a TracePoint mechanism in the Ruby C API.
8 |
9 | ## Requirements
10 |
11 | debase requires Ruby 2.0.0 or higher.
12 |
13 | ## Install
14 |
15 | debase is provided as a RubyGem. To install:
16 |
17 | gem install debase
18 |
19 | ## License
20 |
21 | debase contains parts of the code from ruby-debug-base gem.
22 | See MIT_LICENSE and LICENSE for license information.
23 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require 'rake/testtask'
3 |
4 | BASE_TEST_FILE_LIST = Dir['test/**/test_*.rb']
5 |
6 | # see https://docs.travis-ci.com/user/reference/osx/
7 | # see https://docs.travis-ci.com/user/reference/linux/
8 | # see https://rubies.travis-ci.org/
9 | desc "Generate travis.yml"
10 | task :gen_travis do
11 | matrix = []
12 |
13 | def matrix.macos(image, *versions)
14 | versions.each { |version| self << ['osx', 'osx_image', image, version] }
15 | end
16 |
17 | def matrix.linux(distro, *versions)
18 | versions.each { |version| self << ['linux', 'dist', distro, version] }
19 | end
20 |
21 | since_24 = %w[2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3 3.4-preview2].reverse
22 | since_23 = [*since_24, '2.3']
23 | since_22 = [*since_23, '2.2']
24 | since_21 = [*since_22, '2.1']
25 |
26 | # Ubuntu 16.04
27 | matrix.linux 'xenial', *since_21
28 | # Ubuntu 18.04
29 | matrix.linux 'bionic', *since_23
30 | # Ubuntu 20.04
31 | matrix.linux 'focal', *since_24
32 | # macOS 10.14.6
33 | matrix.macos 'xcode11.3', *since_24
34 | # macOS 10.15.7
35 | matrix.macos 'xcode12.2', *since_24, 'ruby-head'
36 |
37 | puts <<'EOM'
38 | language: ruby
39 | matrix:
40 | fast_finish: true
41 | include:
42 | EOM
43 |
44 | matrix.each do |data|
45 | puts < :clean do
82 | Dir.chdir("ext") do
83 | system("#{Gem.ruby} extconf.rb && make")
84 | exit $?.exitstatus if $?.exitstatus != 0
85 | end
86 | Dir.chdir("ext/attach") do
87 | system("#{Gem.ruby} extconf.rb && make")
88 | exit $?.exitstatus if $?.exitstatus != 0
89 | end
90 | end
91 |
92 | desc "Test debase."
93 | Rake::TestTask.new(:test) do |t|
94 | t.libs += ['./ext', './lib']
95 | t.test_files = FileList[BASE_TEST_FILE_LIST]
96 | t.verbose = true
97 | end
98 | task :test => :lib
99 |
100 | task :default => :test
101 |
--------------------------------------------------------------------------------
/debase.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path("../lib", __FILE__)
3 | require "debase/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "debase"
7 | s.version = Debase::VERSION
8 | s.license = "MIT"
9 | s.authors = ["Alexandr Evstigneev", "Dennis Ushakov"]
10 | s.email = ["hurricup@gmail.com", "dennis.ushakov@gmail.com"]
11 | s.homepage = "https://github.com/ruby-debug/debase"
12 | s.summary = %q{debase is a fast implementation of the standard Ruby debugger debug.rb for Ruby 2.0+}
13 | s.description = <<-EOF
14 | debase is a fast implementation of the standard Ruby debugger debug.rb for Ruby 2.0+.
15 | It is implemented by utilizing a new Ruby TracePoint class. The core component
16 | provides support that front-ends can build on. It provides breakpoint
17 | handling, bindings for stack frames among other things.
18 | EOF
19 |
20 | s.files = `git ls-files`.split("\n")
21 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23 | s.require_paths = ["lib"]
24 |
25 | s.extensions = ["ext/extconf.rb", "ext/attach/extconf.rb"]
26 |
27 | s.required_ruby_version = ">= 2.0"
28 |
29 | s.add_dependency "debase-ruby_core_source", ">= 3.4.1"
30 | s.add_development_dependency "test-unit"
31 | s.add_development_dependency "rake"
32 | end
33 |
--------------------------------------------------------------------------------
/ext/attach/attach.c:
--------------------------------------------------------------------------------
1 | #include "attach.h"
2 |
3 | #ifndef __GNUC__
4 | #define __asm__ asm
5 | #endif
6 |
7 | /*
8 | We need to prevent compiler from optimizing this function calls. For more details
9 | see "noinline" section here: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
10 | */
11 | static void
12 | #if defined(_MSC_VER)
13 | __declspec(noinline)
14 | __func_to_set_breakpoint_at()
15 | {
16 | }
17 | #else
18 | __attribute__((noinline))
19 | __func_to_set_breakpoint_at()
20 | {
21 | __asm__("");
22 | }
23 | #endif
24 |
25 | static void
26 | __catch_line_event(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE klass)
27 | {
28 | (void)sizeof(evflag);
29 | (void)sizeof(self);
30 | (void)sizeof(mid);
31 | (void)sizeof(klass);
32 |
33 | rb_remove_event_hook(__catch_line_event);
34 | if (rb_during_gc())
35 | return;
36 | __func_to_set_breakpoint_at();
37 | }
38 |
39 | int
40 | debase_start_attach()
41 | {
42 | if (rb_during_gc())
43 | return 1;
44 | rb_add_event_hook(__catch_line_event, RUBY_EVENT_LINE, (VALUE) NULL);
45 | return 0;
46 | }
47 |
48 | void
49 | debase_rb_eval(const char *string_to_eval)
50 | {
51 | rb_eval_string_protect(string_to_eval, NULL);
52 | }
53 |
54 | void
55 | Init_attach()
56 | {
57 | /*
58 | The only purpose of this library is to be dlopen'ed inside
59 | gdb/lldb. So no initialization here, you should directly
60 | call functions above.
61 | */
62 | }
63 |
--------------------------------------------------------------------------------
/ext/attach/attach.h:
--------------------------------------------------------------------------------
1 | #ifndef __ATTACH_H__
2 | #define __ATTACH_H__
3 |
4 | #include
5 | #include
6 |
7 | int debase_start_attach();
8 | void debase_rb_eval(const char *);
9 |
10 | #endif //__ATTACH_H__
11 |
--------------------------------------------------------------------------------
/ext/attach/extconf.rb:
--------------------------------------------------------------------------------
1 | if defined?(RUBY_ENGINE) && 'rbx' == RUBY_ENGINE
2 | # create dummy Makefile to indicate success
3 | f = File.open(File.join(File.dirname(__FILE__), "Makefile"), "w")
4 | f.write("all:\n\techo all\ninstall:\n\techo installed\n")
5 | f.close
6 | return
7 | end
8 |
9 | # autodetect ruby headers
10 | unless ARGV.any? {|arg| arg.include?('--with-ruby-include') }
11 | require 'rbconfig'
12 | bindir = RbConfig::CONFIG['bindir']
13 | if bindir =~ %r{(^.*/\.rbenv/versions)/([^/]+)/bin$}
14 | ruby_include = "#{$1}/#{$2}/include/ruby-1.9.1/ruby-#{$2}"
15 | ruby_include = "#{ENV['RBENV_ROOT']}/sources/#{$2}/ruby-#{$2}" unless File.exist?(ruby_include)
16 | ARGV << "--with-ruby-include=#{ruby_include}"
17 | elsif bindir =~ %r{(^.*/\.rvm/rubies)/([^/]+)/bin$}
18 | ruby_include = "#{$1}/#{$2}/include/ruby-1.9.1/#{$2}"
19 | ruby_include = "#{ENV['rvm_path']}/src/#{$2}" unless File.exist?(ruby_include)
20 | ARGV << "--with-ruby-include=#{ruby_include}"
21 | end
22 | end
23 |
24 | require "mkmf"
25 |
26 | RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
27 |
28 | require "debase/ruby_core_source"
29 |
30 | hdrs = proc {
31 | have_header("vm_core.h") and
32 | (have_header("iseq.h") or have_header("iseq.h", ["vm_core.h"])) and
33 | have_header("version.h")
34 | }
35 |
36 | # Allow use customization of compile options. For example, the
37 | # following lines could be put in config_options to to turn off
38 | # optimization:
39 | # $CFLAGS='-fPIC -fno-strict-aliasing -g3 -ggdb -O2 -fPIC'
40 | config_file = File.join(File.dirname(__FILE__), 'config_options.rb')
41 | load config_file if File.exist?(config_file)
42 |
43 | if ENV['debase_debug']
44 | $CFLAGS+=' -Wall -Werror'
45 | $CFLAGS+=' -g3'
46 | end
47 |
48 | dir_config("ruby")
49 | if !Debase::RubyCoreSource.create_makefile_with_core(hdrs, "attach")
50 | STDERR.print("Makefile creation failed\n")
51 | STDERR.print("*************************************************************\n\n")
52 | STDERR.print(" NOTE: If your headers were not found, try passing\n")
53 | STDERR.print(" --with-ruby-include=PATH_TO_HEADERS \n\n")
54 | STDERR.print("*************************************************************\n\n")
55 | exit(1)
56 | end
57 |
--------------------------------------------------------------------------------
/ext/breakpoint.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #ifdef _WIN32
4 | #include
5 | #endif
6 |
7 | #if defined DOSISH
8 | #define isdirsep(x) ((x) == '/' || (x) == '\\')
9 | #else
10 | #define isdirsep(x) ((x) == '/')
11 | #endif
12 |
13 | static VALUE cBreakpoint;
14 | static int breakpoint_max;
15 |
16 | static ID idEval;
17 |
18 | static VALUE
19 | eval_expression(VALUE args)
20 | {
21 | return rb_funcall2(rb_mKernel, idEval, 2, RARRAY_PTR(args));
22 | }
23 |
24 | extern VALUE
25 | catchpoint_hit_count(VALUE catchpoints, VALUE exception, VALUE *exception_name) {
26 | VALUE ancestors;
27 | VALUE expn_class;
28 | VALUE aclass;
29 | VALUE mod_name;
30 | VALUE hit_count;
31 | int i;
32 |
33 | if (catchpoints == Qnil /*|| st_get_num_entries(RHASH_TBL(rdebug_catchpoints)) == 0)*/)
34 | return Qnil;
35 | expn_class = rb_obj_class(exception);
36 | ancestors = rb_mod_ancestors(expn_class);
37 | for(i = 0; i < RARRAY_LENINT(ancestors); i++)
38 | {
39 | aclass = rb_ary_entry(ancestors, i);
40 | mod_name = rb_mod_name(aclass);
41 | hit_count = rb_hash_aref(catchpoints, mod_name);
42 | if(hit_count != Qnil)
43 | {
44 | *exception_name = mod_name;
45 | return hit_count;
46 | }
47 | }
48 | return Qnil;
49 | }
50 |
51 | static void
52 | Breakpoint_mark(breakpoint_t *breakpoint)
53 | {
54 | rb_gc_mark(breakpoint->source);
55 | rb_gc_mark(breakpoint->expr);
56 | }
57 |
58 | static VALUE
59 | Breakpoint_create(VALUE klass)
60 | {
61 | breakpoint_t *breakpoint;
62 |
63 | breakpoint = ALLOC(breakpoint_t);
64 | return Data_Wrap_Struct(klass, Breakpoint_mark, xfree, breakpoint);
65 | }
66 |
67 | static VALUE
68 | Breakpoint_initialize(VALUE self, VALUE source, VALUE pos, VALUE expr)
69 | {
70 | breakpoint_t *breakpoint;
71 |
72 | Data_Get_Struct(self, breakpoint_t, breakpoint);
73 | breakpoint->id = ++breakpoint_max;
74 | breakpoint->source = StringValue(source);
75 | breakpoint->line = FIX2INT(pos);
76 | breakpoint->enabled = Qtrue;
77 | breakpoint->expr = NIL_P(expr) ? expr : StringValue(expr);
78 |
79 | return Qnil;
80 | }
81 |
82 | static VALUE
83 | Breakpoint_remove(VALUE self, VALUE breakpoints, VALUE id_value)
84 | {
85 | int i;
86 | int id;
87 | VALUE breakpoint_object;
88 | breakpoint_t *breakpoint;
89 |
90 | if (breakpoints == Qnil) return Qnil;
91 |
92 | id = FIX2INT(id_value);
93 |
94 | for(i = 0; i < RARRAY_LENINT(breakpoints); i++)
95 | {
96 | breakpoint_object = rb_ary_entry(breakpoints, i);
97 | Data_Get_Struct(breakpoint_object, breakpoint_t, breakpoint);
98 | if(breakpoint->id == id)
99 | {
100 | rb_ary_delete_at(breakpoints, i);
101 | return breakpoint_object;
102 | }
103 | }
104 | return Qnil;
105 | }
106 |
107 | static VALUE
108 | Breakpoint_id(VALUE self)
109 | {
110 | breakpoint_t *breakpoint;
111 |
112 | Data_Get_Struct(self, breakpoint_t, breakpoint);
113 | return INT2FIX(breakpoint->id);
114 | }
115 |
116 | static VALUE
117 | Breakpoint_source(VALUE self)
118 | {
119 | breakpoint_t *breakpoint;
120 |
121 | Data_Get_Struct(self, breakpoint_t, breakpoint);
122 | return breakpoint->source;
123 | }
124 |
125 | static VALUE
126 | Breakpoint_expr_get(VALUE self)
127 | {
128 | breakpoint_t *breakpoint;
129 |
130 | Data_Get_Struct(self, breakpoint_t, breakpoint);
131 | return breakpoint->expr;
132 | }
133 |
134 | static VALUE
135 | Breakpoint_expr_set(VALUE self, VALUE new_val)
136 | {
137 | breakpoint_t *breakpoint;
138 |
139 | Data_Get_Struct(self, breakpoint_t, breakpoint);
140 | breakpoint->expr = new_val;
141 | return breakpoint->expr;
142 | }
143 |
144 | static VALUE
145 | Breakpoint_enabled_set(VALUE self, VALUE new_val)
146 | {
147 | breakpoint_t *breakpoint;
148 |
149 | Data_Get_Struct(self, breakpoint_t, breakpoint);
150 | breakpoint->enabled = new_val;
151 | return breakpoint->enabled;
152 | }
153 |
154 | static VALUE
155 | Breakpoint_enabled_get(VALUE self)
156 | {
157 | breakpoint_t *breakpoint;
158 |
159 | Data_Get_Struct(self, breakpoint_t, breakpoint);
160 | return breakpoint->enabled;
161 | }
162 |
163 | static VALUE
164 | Breakpoint_pos(VALUE self)
165 | {
166 | breakpoint_t *breakpoint;
167 |
168 | Data_Get_Struct(self, breakpoint_t, breakpoint);
169 | return INT2FIX(breakpoint->line);
170 | }
171 |
172 | int
173 | filename_cmp_impl(VALUE source, char *file)
174 | {
175 | char *source_ptr, *file_ptr;
176 | long s_len, f_len, min_len;
177 | long s,f;
178 | int dirsep_flag = 0;
179 |
180 | s_len = RSTRING_LEN(source);
181 | f_len = strlen(file);
182 | min_len = s_len < f_len ? s_len : f_len;
183 |
184 | source_ptr = RSTRING_PTR(source);
185 | file_ptr = file;
186 |
187 | for( s = s_len - 1, f = f_len - 1; s >= s_len - min_len && f >= f_len - min_len; s--, f-- )
188 | {
189 | if((source_ptr[s] == '.' || file_ptr[f] == '.') && dirsep_flag)
190 | return 1;
191 | if(isdirsep(source_ptr[s]) && isdirsep(file_ptr[f]))
192 | dirsep_flag = 1;
193 | #ifdef DOSISH_DRIVE_LETTER
194 | else if (s == 0)
195 | return(toupper(source_ptr[s]) == toupper(file_ptr[f]));
196 | #endif
197 | else if(source_ptr[s] != file_ptr[f])
198 | return 0;
199 | }
200 | return 1;
201 | }
202 |
203 | int
204 | filename_cmp(VALUE source, char *file)
205 | {
206 | #ifdef _WIN32
207 | return filename_cmp_impl(source, file);
208 | #else
209 | #ifdef PATH_MAX
210 | char path[PATH_MAX + 1];
211 | path[PATH_MAX] = 0;
212 | return filename_cmp_impl(source, realpath(file, path) != NULL ? path : file);
213 | #else
214 | char *path;
215 | int result;
216 | path = realpath(file, NULL);
217 | result = filename_cmp_impl(source, path == NULL ? file : path);
218 | free(path);
219 | return result;
220 | #endif
221 | #endif
222 | }
223 |
224 | static int
225 | check_breakpoint_by_pos(VALUE breakpoint_object, char *file, int line)
226 | {
227 | breakpoint_t *breakpoint;
228 |
229 | if(breakpoint_object == Qnil)
230 | return 0;
231 | Data_Get_Struct(breakpoint_object, breakpoint_t, breakpoint);
232 | if (Qtrue != breakpoint->enabled) return 0;
233 | if(breakpoint->line != line)
234 | return 0;
235 | if(filename_cmp(breakpoint->source, file))
236 | return 1;
237 | return 0;
238 | }
239 |
240 | static int
241 | check_breakpoint_expr(VALUE breakpoint_object, VALUE trace_point)
242 | {
243 | breakpoint_t *breakpoint;
244 | VALUE binding, args, result;
245 | int error;
246 |
247 | if(breakpoint_object == Qnil) return 0;
248 | Data_Get_Struct(breakpoint_object, breakpoint_t, breakpoint);
249 | if (Qtrue != breakpoint->enabled) return 0;
250 | if (NIL_P(breakpoint->expr)) return 1;
251 |
252 | if (NIL_P(trace_point)) {
253 | binding = rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING"));
254 | } else {
255 | binding = rb_tracearg_binding(rb_tracearg_from_tracepoint(trace_point));
256 | }
257 |
258 | args = rb_ary_new3(2, breakpoint->expr, binding);
259 | result = rb_protect(eval_expression, args, &error);
260 | if(error){
261 | rb_set_errinfo(Qnil);
262 | return 0;
263 | }
264 |
265 | return RTEST(result);
266 | }
267 |
268 | static VALUE
269 | Breakpoint_find(VALUE self, VALUE breakpoints, VALUE source, VALUE pos, VALUE trace_point)
270 | {
271 | return breakpoint_find(breakpoints, source, pos, trace_point);
272 | }
273 |
274 | extern VALUE
275 | breakpoint_find(VALUE breakpoints, VALUE source, VALUE pos, VALUE trace_point)
276 | {
277 | VALUE breakpoint_object;
278 | char *file;
279 | int line;
280 | int i;
281 |
282 | file = RSTRING_PTR(source);
283 | line = FIX2INT(pos);
284 | for(i = 0; i < RARRAY_LENINT(breakpoints); i++)
285 | {
286 | breakpoint_object = rb_ary_entry(breakpoints, i);
287 | if (check_breakpoint_by_pos(breakpoint_object, file, line) &&
288 | check_breakpoint_expr(breakpoint_object, trace_point))
289 | {
290 | return breakpoint_object;
291 | }
292 | }
293 | return Qnil;
294 | }
295 |
296 | extern void
297 | breakpoint_init_variables()
298 | {
299 | breakpoint_max = 0;
300 | }
301 |
302 | extern void
303 | Init_breakpoint(VALUE mDebase)
304 | {
305 | breakpoint_init_variables();
306 | cBreakpoint = rb_define_class_under(mDebase, "Breakpoint", rb_cObject);
307 | rb_define_singleton_method(cBreakpoint, "find", Breakpoint_find, 4);
308 | rb_define_singleton_method(cBreakpoint, "remove", Breakpoint_remove, 2);
309 | rb_define_method(cBreakpoint, "initialize", Breakpoint_initialize, 3);
310 | rb_define_method(cBreakpoint, "id", Breakpoint_id, 0);
311 | rb_define_method(cBreakpoint, "source", Breakpoint_source, 0);
312 | rb_define_method(cBreakpoint, "pos", Breakpoint_pos, 0);
313 |
314 | /* */
315 | rb_define_method(cBreakpoint, "expr", Breakpoint_expr_get, 0);
316 | rb_define_method(cBreakpoint, "expr=", Breakpoint_expr_set, 1);
317 | rb_define_method(cBreakpoint, "enabled", Breakpoint_enabled_get, 0);
318 | rb_define_method(cBreakpoint, "enabled=", Breakpoint_enabled_set, 1);
319 | /* */
320 |
321 | rb_define_alloc_func(cBreakpoint, Breakpoint_create);
322 |
323 | idEval = rb_intern("eval");
324 | }
325 |
--------------------------------------------------------------------------------
/ext/context.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | static VALUE cContext;
4 | static int thnum_current = 0;
5 |
6 | static VALUE idAlive;
7 |
8 | /* "Step", "Next" and "Finish" do their work by saving information
9 | about where to stop next. reset_stopping_points removes/resets this
10 | information. */
11 | extern void
12 | reset_stepping_stop_points(debug_context_t *context)
13 | {
14 | context->dest_frame = -1;
15 | context->stop_line = -1;
16 | context->stop_next = -1;
17 | }
18 |
19 | static inline VALUE
20 | Context_thnum(VALUE self) {
21 | debug_context_t *context;
22 | Data_Get_Struct(self, debug_context_t, context);
23 | return INT2FIX(context->thnum);
24 | }
25 |
26 | static inline void
27 | fill_frame(debug_frame_t *frame, const char* file, int line, VALUE binding, VALUE self)
28 | {
29 | frame->file = file;
30 | frame->line = line;
31 | frame->binding = binding;
32 | frame->self = self;
33 | }
34 |
35 | extern void
36 | fill_stack(debug_context_t *context, const rb_debug_inspector_t *inspector) {
37 | debug_frame_t *frame;
38 | VALUE locations;
39 | VALUE location;
40 | VALUE path;
41 | VALUE lineno;
42 | const char *file;
43 | int line;
44 | int stack_size;
45 | int i;
46 |
47 | locations = rb_debug_inspector_backtrace_locations(inspector);
48 | stack_size = locations == Qnil ? 0 : RARRAY_LENINT(locations);
49 | context->stack_size = 0;
50 |
51 | for (i = 0; i < stack_size; i++) {
52 |
53 | location = rb_ary_entry(locations, i);
54 | VALUE iseq = rb_debug_inspector_frame_iseq_get(inspector, i);
55 |
56 | if(iseq != Qnil)
57 | {
58 | frame = ALLOC(debug_frame_t);
59 | path = rb_funcall(location, rb_intern("path"), 0);
60 | lineno = rb_funcall(location, rb_intern("lineno"), 0);
61 | file = path != Qnil ? RSTRING_PTR(path) : "";
62 | line = FIX2INT(lineno);
63 |
64 | fill_frame(frame, file, line, rb_debug_inspector_frame_binding_get(inspector, i), rb_debug_inspector_frame_self_get(inspector, i));
65 | frame->prev = context->stack;
66 | context->stack = frame;
67 | context->stack_size++;
68 | }
69 | }
70 | }
71 |
72 | extern void
73 | clear_stack(debug_context_t *context)
74 | {
75 | debug_frame_t *frame;
76 | debug_frame_t *prev;
77 |
78 | frame = context->stack;
79 | while(frame != NULL) {
80 | prev = frame->prev;
81 | xfree(frame);
82 | frame = prev;
83 | }
84 | context->stack = NULL;
85 | }
86 |
87 | static inline VALUE
88 | Context_stack_size(VALUE self)
89 | {
90 | debug_context_t *context;
91 | Data_Get_Struct(self, debug_context_t, context);
92 | return INT2FIX(context->stack_size);
93 | }
94 |
95 | static inline VALUE
96 | Context_thread(VALUE self)
97 | {
98 | debug_context_t *context;
99 | Data_Get_Struct(self, debug_context_t, context);
100 | return context->thread;
101 | }
102 |
103 | static inline VALUE
104 | Context_dead(VALUE self)
105 | {
106 | debug_context_t *context;
107 | Data_Get_Struct(self, debug_context_t, context);
108 | return IS_THREAD_ALIVE(context->thread) ? Qfalse : Qtrue;
109 | }
110 |
111 | extern VALUE
112 | Context_ignored(VALUE self)
113 | {
114 | debug_context_t *context;
115 |
116 | if (self == Qnil) return Qtrue;
117 | Data_Get_Struct(self, debug_context_t, context);
118 | return CTX_FL_TEST(context, CTX_FL_IGNORE) ? Qtrue : Qfalse;
119 | }
120 |
121 | static void
122 | Context_mark(debug_context_t *context)
123 | {
124 | debug_frame_t *frame;
125 |
126 | rb_gc_mark(context->thread);
127 | frame = context->stack;
128 | while(frame != NULL)
129 | {
130 | rb_gc_mark(frame->self);
131 | rb_gc_mark(frame->binding);
132 | frame = frame->prev;
133 | }
134 | }
135 |
136 | static void
137 | Context_free(debug_context_t *context) {
138 | xfree(context);
139 | }
140 |
141 | extern VALUE
142 | context_create(VALUE thread, VALUE cDebugThread) {
143 | debug_context_t *context;
144 | VALUE locations;
145 |
146 | context = ALLOC(debug_context_t);
147 | context->stack_size = 0;
148 | locations = rb_funcall(thread, rb_intern("backtrace_locations"), 1, INT2FIX(1));
149 | context->calced_stack_size = locations != Qnil ? RARRAY_LENINT(locations) : 0;
150 | context->init_stack_size = -1;
151 |
152 | context->stack = NULL;
153 | context->thnum = ++thnum_current;
154 | context->thread = thread;
155 | context->flags = 0;
156 | context->last_file = NULL;
157 | context->last_line = -1;
158 | context->hit_user_code = 0;
159 | context->script_finished = 0;
160 | context->stop_frame = -1;
161 | context->thread_pause = 0;
162 | context->stop_reason = CTX_STOP_NONE;
163 | reset_stepping_stop_points(context);
164 | if(rb_obj_class(thread) == cDebugThread) CTX_FL_SET(context, CTX_FL_IGNORE);
165 | return Data_Wrap_Struct(cContext, Context_mark, Context_free, context);
166 | }
167 |
168 | static inline void
169 | check_frame_number_valid(debug_context_t *context, int frame_no)
170 | {
171 | if (frame_no < 0 || frame_no >= context->stack_size) {
172 | rb_raise(rb_eArgError, "Invalid frame number %d, stack (0...%d)",
173 | frame_no, context->stack_size);
174 | }
175 | }
176 |
177 | static debug_frame_t*
178 | get_frame_no(debug_context_t *context, int frame_no)
179 | {
180 | debug_frame_t *frame;
181 | int i;
182 |
183 | check_frame_number_valid(context, frame_no);
184 | frame = context->stack;
185 | for (i = 0; i < context->stack_size - frame_no - 1; i++) {
186 | frame = frame->prev;
187 | }
188 | return frame;
189 | }
190 |
191 | static VALUE
192 | Context_frame_file(int argc, VALUE *argv, VALUE self)
193 | {
194 | debug_context_t *context;
195 | debug_frame_t *frame;
196 | VALUE frame_no;
197 | int frame_n;
198 |
199 | Data_Get_Struct(self, debug_context_t, context);
200 | frame_n = rb_scan_args(argc, argv, "01", &frame_no) == 0 ? 0 : FIX2INT(frame_no);
201 | frame = get_frame_no(context, frame_n);
202 | return rb_str_new2(frame->file);
203 | }
204 |
205 | static VALUE
206 | Context_frame_line(int argc, VALUE *argv, VALUE self)
207 | {
208 | debug_context_t *context;
209 | debug_frame_t *frame;
210 | VALUE frame_no;
211 | int frame_n;
212 |
213 | Data_Get_Struct(self, debug_context_t, context);
214 | frame_n = rb_scan_args(argc, argv, "01", &frame_no) == 0 ? 0 : FIX2INT(frame_no);
215 | frame = get_frame_no(context, frame_n);
216 | return INT2FIX(frame->line);
217 | }
218 |
219 | static VALUE
220 | Context_frame_binding(int argc, VALUE *argv, VALUE self)
221 | {
222 | debug_context_t *context;
223 | debug_frame_t *frame;
224 | VALUE frame_no;
225 | int frame_n;
226 |
227 | Data_Get_Struct(self, debug_context_t, context);
228 | frame_n = rb_scan_args(argc, argv, "01", &frame_no) == 0 ? 0 : FIX2INT(frame_no);
229 | frame = get_frame_no(context, frame_n);
230 | return frame->binding;
231 | }
232 |
233 | static VALUE
234 | Context_frame_self(int argc, VALUE *argv, VALUE self)
235 | {
236 | debug_context_t *context;
237 | debug_frame_t *frame;
238 | VALUE frame_no;
239 | int frame_n;
240 |
241 | Data_Get_Struct(self, debug_context_t, context);
242 | frame_n = rb_scan_args(argc, argv, "01", &frame_no) == 0 ? 0 : FIX2INT(frame_no);
243 | frame = get_frame_no(context, frame_n);
244 | return frame->self;
245 | }
246 |
247 | static VALUE
248 | Context_stop_reason(VALUE self)
249 | {
250 | debug_context_t *context;
251 | const char *symbol;
252 |
253 | Data_Get_Struct(self, debug_context_t, context);
254 |
255 | switch(context->stop_reason)
256 | {
257 | case CTX_STOP_STEP:
258 | symbol = "step";
259 | break;
260 | case CTX_STOP_BREAKPOINT:
261 | symbol = "breakpoint";
262 | break;
263 | case CTX_STOP_CATCHPOINT:
264 | symbol = "catchpoint";
265 | break;
266 | case CTX_STOP_NONE:
267 | default:
268 | symbol = "none";
269 | }
270 | if(CTX_FL_TEST(context, CTX_FL_DEAD))
271 | symbol = "post-mortem";
272 |
273 | return ID2SYM(rb_intern(symbol));
274 | }
275 |
276 | static VALUE
277 | Context_pause(VALUE self)
278 | {
279 | debug_context_t *context;
280 |
281 | Data_Get_Struct(self, debug_context_t, context);
282 |
283 | if (context->thread == rb_thread_current())
284 | {
285 | return Qfalse;
286 | }
287 |
288 | enable_trace_points();
289 | context->thread_pause = 1;
290 | return Qtrue;
291 | }
292 |
293 | static VALUE
294 | Context_stop_next(int argc, VALUE *argv, VALUE self)
295 | {
296 | VALUE steps;
297 | VALUE force;
298 | debug_context_t *context;
299 |
300 | rb_scan_args(argc, argv, "11", &steps, &force);
301 | if(FIX2INT(steps) < 0) rb_raise(rb_eRuntimeError, "Steps argument can't be negative.");
302 |
303 | Data_Get_Struct(self, debug_context_t, context);
304 |
305 | context->stop_next = FIX2INT(steps);
306 |
307 |
308 | if(RTEST(force))
309 | CTX_FL_SET(context, CTX_FL_FORCE_MOVE);
310 | else
311 | CTX_FL_UNSET(context, CTX_FL_FORCE_MOVE);
312 |
313 | return steps;
314 | }
315 |
316 | static VALUE
317 | Context_step_over(int argc, VALUE *argv, VALUE self)
318 | {
319 | VALUE lines, frame, force;
320 | debug_context_t *context;
321 |
322 | Data_Get_Struct(self, debug_context_t, context);
323 |
324 | if(context->stack_size == 0)
325 | rb_raise(rb_eRuntimeError, "No frames collected.");
326 |
327 | rb_scan_args(argc, argv, "12", &lines, &frame, &force);
328 | context->stop_line = FIX2INT(lines);
329 | CTX_FL_UNSET(context, CTX_FL_STEPPED);
330 | if (frame == Qnil)
331 | {
332 | context->dest_frame = context->calced_stack_size;
333 | }
334 | else
335 | {
336 | if (FIX2INT(frame) < 0 && FIX2INT(frame) >= context->calced_stack_size)
337 | rb_raise(rb_eRuntimeError, "Destination frame is out of range.");
338 | context->dest_frame = context->calced_stack_size - FIX2INT(frame);
339 | }
340 | if(RTEST(force))
341 | CTX_FL_SET(context, CTX_FL_FORCE_MOVE);
342 | else
343 | CTX_FL_UNSET(context, CTX_FL_FORCE_MOVE);
344 |
345 | return Qnil;
346 | }
347 |
348 | static VALUE
349 | Context_stop_frame(VALUE self, VALUE frame)
350 | {
351 | debug_context_t *debug_context;
352 |
353 | Data_Get_Struct(self, debug_context_t, debug_context);
354 |
355 | if(FIX2INT(frame) < 0 && FIX2INT(frame) >= debug_context->calced_stack_size)
356 | rb_raise(rb_eRuntimeError, "Stop frame is out of range.");
357 | /* we decrease stack size by frame and 1 because we use stop_frame after
358 | updating stack size. If that code will be changed this should be changed accordingly.
359 | */
360 | debug_context->stop_frame = debug_context->calced_stack_size - FIX2INT(frame) - 1;
361 |
362 | return frame;
363 | }
364 |
365 | extern void
366 | context_init_variables()
367 | {
368 | thnum_current = 0;
369 | }
370 |
371 | /*
372 | * Document-class: Context
373 | *
374 | * == Summary
375 | *
376 | * Debugger keeps a single instance of this class for each Ruby thread.
377 | */
378 | VALUE
379 | Init_context(VALUE mDebase)
380 | {
381 | cContext = rb_define_class_under(mDebase, "Context", rb_cObject);
382 | rb_define_method(cContext, "stack_size", Context_stack_size, 0);
383 | rb_define_method(cContext, "thread", Context_thread, 0);
384 | rb_define_method(cContext, "dead?", Context_dead, 0);
385 | rb_define_method(cContext, "ignored?", Context_ignored, 0);
386 | rb_define_method(cContext, "thnum", Context_thnum, 0);
387 | rb_define_method(cContext, "stop_reason", Context_stop_reason, 0);
388 | rb_define_method(cContext, "frame_file", Context_frame_file, -1);
389 | rb_define_method(cContext, "frame_line", Context_frame_line, -1);
390 | rb_define_method(cContext, "frame_binding", Context_frame_binding, -1);
391 | rb_define_method(cContext, "frame_self", Context_frame_self, -1);
392 | rb_define_method(cContext, "stop_next=", Context_stop_next, -1);
393 | rb_define_method(cContext, "step", Context_stop_next, -1);
394 | rb_define_method(cContext, "step_over", Context_step_over, -1);
395 | rb_define_method(cContext, "stop_frame=", Context_stop_frame, 1);
396 | rb_define_method(cContext, "pause", Context_pause, 0);
397 |
398 | idAlive = rb_intern("alive?");
399 | context_init_variables();
400 |
401 | return cContext;
402 | // rb_define_method(cContext, "suspend", context_suspend, 0);
403 | // rb_define_method(cContext, "suspended?", context_is_suspended, 0);
404 | // rb_define_method(cContext, "resume", context_resume, 0);
405 | // rb_define_method(cContext, "tracing", context_tracing, 0);
406 | // rb_define_method(cContext, "tracing=", context_set_tracing, 1);
407 | // rb_define_method(cContext, "ignored?", context_ignored, 0);
408 | // rb_define_method(cContext, "frame_args", context_frame_args, -1);
409 | // rb_define_method(cContext, "frame_args_info", context_frame_args_info, -1);
410 | // rb_define_method(cContext, "frame_class", context_frame_class, -1);
411 | // rb_define_method(cContext, "frame_id", context_frame_id, -1);
412 | // rb_define_method(cContext, "frame_locals", context_frame_locals, -1);
413 | // rb_define_method(cContext, "frame_method", context_frame_id, -1);
414 | // rb_define_method(cContext, "breakpoint",
415 | // context_breakpoint, 0); /* in breakpoint.c */
416 | // rb_define_method(cContext, "set_breakpoint",
417 | // context_set_breakpoint, -1); /* in breakpoint.c */
418 | // rb_define_method(cContext, "jump", context_jump, 2);
419 | // rb_define_method(cContext, "pause", context_pause, 0);
420 | }
421 |
--------------------------------------------------------------------------------
/ext/debase_internals.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | static VALUE mDebase; /* Ruby Debase Module object */
5 | static VALUE cContext;
6 | static VALUE cDebugThread;
7 |
8 | static VALUE verbose = Qfalse;
9 | static VALUE locker = Qnil;
10 | static VALUE contexts;
11 | static VALUE catchpoints;
12 | static VALUE breakpoints;
13 | static VALUE file_filter_enabled = Qfalse;
14 |
15 | static VALUE tpLine;
16 | static VALUE tpCall;
17 | static VALUE tpReturn;
18 | static VALUE tpRaise;
19 |
20 | static VALUE idAlive;
21 | static VALUE idAtLine;
22 | static VALUE idAtBreakpoint;
23 | static VALUE idAtCatchpoint;
24 | static VALUE idFileFilter;
25 | static VALUE idAccept;
26 |
27 | static int started = 0;
28 |
29 | static void
30 | print_debug(const char *message, ...)
31 | {
32 | va_list ap;
33 |
34 | if (verbose == Qfalse) return;
35 | va_start(ap, message);
36 | vfprintf(stderr, message, ap);
37 | va_end(ap);
38 | }
39 |
40 | static inline int
41 | check_stop_frame(debug_context_t *context) {
42 | return context->calced_stack_size == context->stop_frame && context->calced_stack_size >= 0;
43 | }
44 |
45 | static VALUE
46 | is_path_accepted(VALUE path) {
47 | VALUE filter;
48 | if (file_filter_enabled == Qfalse) return Qtrue;
49 | filter = rb_funcall(mDebase, idFileFilter, 0);
50 | return rb_funcall(filter, idAccept, 1, path);
51 | }
52 |
53 | static VALUE
54 | Debase_thread_context(VALUE self, VALUE thread)
55 | {
56 | VALUE context;
57 |
58 | context = rb_hash_aref(contexts, thread);
59 | if (context == Qnil) {
60 | context = context_create(thread, cDebugThread);
61 | rb_hash_aset(contexts, thread, context);
62 | }
63 | return context;
64 | }
65 |
66 | static VALUE
67 | Debase_current_context(VALUE self)
68 | {
69 | return Debase_thread_context(self, rb_thread_current());
70 | }
71 |
72 | static int
73 | set_recalc_flag(VALUE thread, VALUE context_object, VALUE ignored)
74 | {
75 | debug_context_t *context;
76 |
77 | Data_Get_Struct(context_object, debug_context_t, context);
78 | CTX_FL_SET(context, CTX_FL_UPDATE_STACK);
79 |
80 | return ST_CONTINUE;
81 | }
82 |
83 | static int
84 | can_disable_trace_points(VALUE thread, VALUE context_object, VALUE result)
85 | {
86 | debug_context_t *context;
87 |
88 | Data_Get_Struct(context_object, debug_context_t, context);
89 |
90 | if (-1 != context->dest_frame
91 | || context->stop_line >= 0
92 | || -1 != context->stop_next
93 | || context->stop_reason != CTX_STOP_NONE
94 | || context->thread_pause != 0)
95 | {
96 | print_debug("can_disable_tp: %d %d %d %d\n", context->dest_frame, context->stop_line,
97 | context->stop_next, context->stop_reason);
98 | *((VALUE *) result) = Qfalse;
99 | return ST_STOP;
100 | }
101 | return ST_CONTINUE;
102 | }
103 |
104 | static void
105 | try_disable_trace_points()
106 | {
107 | VALUE can_disable = Qtrue;
108 |
109 | if (RARRAY_LEN(breakpoints) != 0) return;
110 | print_debug("disable_tps: no breakpoints\n");
111 | if (!RHASH_EMPTY_P(catchpoints)) return;
112 | print_debug("disable_tps: no catchpoints\n");
113 | if (rb_tracepoint_enabled_p(tpLine) == Qfalse) return;
114 | print_debug("disable_tps: tps are enabled\n");
115 |
116 | rb_hash_foreach(contexts, can_disable_trace_points, (VALUE)&can_disable);
117 | if (can_disable == Qfalse) return;
118 | print_debug("disable_tps: can disable contexts\n");
119 |
120 | rb_tracepoint_disable(tpLine);
121 | rb_tracepoint_disable(tpCall);
122 | rb_tracepoint_disable(tpReturn);
123 | rb_tracepoint_disable(tpRaise);
124 | rb_hash_foreach(contexts, set_recalc_flag, 0);
125 | }
126 |
127 | extern VALUE
128 | enable_trace_points()
129 | {
130 | print_debug("enable_tps: \n");
131 | if (rb_tracepoint_enabled_p(tpLine) == Qtrue) return Qtrue;
132 | print_debug("enable_tps: need to enable\n");
133 |
134 | rb_tracepoint_enable(tpLine);
135 | rb_tracepoint_enable(tpCall);
136 | rb_tracepoint_enable(tpReturn);
137 | rb_tracepoint_enable(tpRaise);
138 |
139 | return Qfalse;
140 | }
141 |
142 | static VALUE
143 | Debase_enable_trace_points(VALUE self)
144 | {
145 | return enable_trace_points();
146 | }
147 |
148 | static int
149 | remove_dead_threads(VALUE thread, VALUE context, VALUE ignored)
150 | {
151 | return (IS_THREAD_ALIVE(thread)) ? ST_CONTINUE : ST_DELETE;
152 | }
153 |
154 | static void
155 | cleanup(debug_context_t *context)
156 | {
157 | VALUE thread;
158 |
159 | context->stop_reason = CTX_STOP_NONE;
160 |
161 | clear_stack(context);
162 |
163 | /* release a lock */
164 | locker = Qnil;
165 |
166 | /* let the next thread to run */
167 | thread = remove_from_locked();
168 | if(thread != Qnil)
169 | rb_thread_run(thread);
170 |
171 | try_disable_trace_points();
172 | }
173 |
174 | static int
175 | check_start_processing(debug_context_t *context, VALUE thread)
176 | {
177 | /* return if thread is marked as 'ignored'.
178 | debugger's threads are marked this way
179 | */
180 | if(CTX_FL_TEST(context, CTX_FL_IGNORE)) return 0;
181 |
182 | while(1)
183 | {
184 | /* halt execution of the current thread if the debugger
185 | is activated in another
186 | */
187 | while(locker != Qnil && locker != thread)
188 | {
189 | add_to_locked(thread);
190 | rb_thread_stop();
191 | }
192 |
193 | /* stop the current thread if it's marked as suspended */
194 | if(CTX_FL_TEST(context, CTX_FL_SUSPEND) && locker != thread)
195 | {
196 | CTX_FL_SET(context, CTX_FL_WAS_RUNNING);
197 | rb_thread_stop();
198 | }
199 | else break;
200 | }
201 |
202 | /* return if the current thread is the locker */
203 | if(locker != Qnil) return 0;
204 |
205 | /* only the current thread can proceed */
206 | locker = thread;
207 |
208 | /* ignore a skipped section of code */
209 | if(CTX_FL_TEST(context, CTX_FL_SKIPPED)) {
210 | cleanup(context);
211 | return 0;
212 | }
213 | return 1;
214 | }
215 |
216 | static inline const char*
217 | symbol2str(VALUE symbol)
218 | {
219 | VALUE id;
220 | static const char* nil_str= "nil";
221 | if (symbol == Qnil) {
222 | return nil_str;
223 | }
224 | id = SYM2ID(symbol);
225 | if (symbol == Qnil) {
226 | return nil_str;
227 | }
228 | return rb_id2name(id);
229 | }
230 |
231 | static inline void
232 | print_event(rb_trace_point_t *tp, debug_context_t *context)
233 | {
234 | VALUE locations;
235 | VALUE path;
236 | VALUE line;
237 | VALUE event;
238 | VALUE mid;
239 | VALUE rb_cl;
240 | VALUE rb_cl_name;
241 | const char *defined_class;
242 |
243 | if (verbose == Qtrue) {
244 | path = rb_tracearg_path(tp);
245 | line = rb_tracearg_lineno(tp);
246 | event = rb_tracearg_event(tp);
247 | mid = rb_tracearg_method_id(tp);
248 | rb_cl = rb_tracearg_defined_class(tp);
249 | rb_cl_name = NIL_P(rb_cl) ? rb_cl : rb_mod_name(rb_cl);
250 | defined_class = NIL_P(rb_cl_name) ? "" : RSTRING_PTR(rb_cl_name);
251 |
252 | fprintf(stderr, "[#%d] %s@%s:%d %s#%s\n",
253 | context->thnum,
254 | symbol2str(event),
255 | path == Qnil ? "" : RSTRING_PTR(path),
256 | FIX2INT(line),
257 | defined_class,
258 | mid == Qnil ? "(top level)" : symbol2str(mid)
259 | );
260 | locations = rb_funcall(context->thread, rb_intern("backtrace_locations"), 1, INT2FIX(1));
261 | fprintf(stderr, " calced_stack_size=%d, stack_size=%d, real_stack_size=%d\n",
262 | context->calced_stack_size, context->stack_size,
263 | locations != Qnil ? RARRAY_LENINT(locations) : 0);
264 | }
265 | }
266 |
267 | static VALUE
268 | fill_stack_and_invoke(const rb_debug_inspector_t *inspector, void *data)
269 | {
270 | debug_context_t *context;
271 | VALUE context_object;
272 |
273 | context_object = *(VALUE *)data;
274 | Data_Get_Struct(context_object, debug_context_t, context);
275 | fill_stack(context, inspector);
276 |
277 | return Qnil;
278 | }
279 |
280 | static VALUE
281 | start_inspector(VALUE data)
282 | {
283 | return rb_debug_inspector_open(fill_stack_and_invoke, &data);
284 | }
285 |
286 | static VALUE
287 | stop_inspector(VALUE data)
288 | {
289 | return Qnil;
290 | }
291 |
292 | static int
293 | remove_pause_flag(VALUE thread, VALUE context_object, VALUE ignored)
294 | {
295 | debug_context_t *context;
296 |
297 | Data_Get_Struct(context_object, debug_context_t, context);
298 | context->thread_pause = 0;
299 |
300 | return ST_CONTINUE;
301 | }
302 |
303 | static void
304 | call_at_line(debug_context_t *context, char *file, int line, VALUE context_object)
305 | {
306 | context->hit_user_code = 1;
307 |
308 | rb_hash_foreach(contexts, remove_pause_flag, 0);
309 | CTX_FL_UNSET(context, CTX_FL_STEPPED);
310 | CTX_FL_UNSET(context, CTX_FL_FORCE_MOVE);
311 | context->last_file = file;
312 | context->last_line = line;
313 | rb_funcall(context_object, idAtLine, 2, rb_str_new2(file), INT2FIX(line));
314 | }
315 |
316 | int count_stack_size() {
317 | rb_thread_t *thread = ruby_current_thread;
318 | rb_control_frame_t *last_cfp = TH_CFP(thread);
319 | const rb_control_frame_t *start_cfp = (rb_control_frame_t *)RUBY_VM_END_CONTROL_FRAME(TH_INFO(thread));
320 | const rb_control_frame_t *cfp;
321 |
322 | ptrdiff_t size, i;
323 |
324 | start_cfp =
325 | RUBY_VM_NEXT_CONTROL_FRAME(
326 | RUBY_VM_NEXT_CONTROL_FRAME(start_cfp)); /* skip top frames */
327 |
328 | if (start_cfp < last_cfp) {
329 | size = 0;
330 | }
331 | else {
332 | size = start_cfp - last_cfp + 1;
333 | }
334 |
335 | int stack_size = 0;
336 | for (i=0, cfp = start_cfp; iiseq && cfp->pc) {
338 | stack_size++;
339 | }
340 | }
341 |
342 | return stack_size;
343 | }
344 |
345 | static void
346 | process_line_event(VALUE trace_point, void *data)
347 | {
348 | VALUE path;
349 | VALUE lineno;
350 | VALUE context_object;
351 | VALUE breakpoint;
352 | debug_context_t *context;
353 | rb_trace_point_t *tp;
354 | char *file;
355 | int line;
356 | int moved;
357 |
358 | context_object = Debase_current_context(mDebase);
359 | Data_Get_Struct(context_object, debug_context_t, context);
360 | if (!check_start_processing(context, rb_thread_current())) return;
361 |
362 | tp = TRACE_POINT;
363 | path = rb_tracearg_path(tp);
364 |
365 | if (is_path_accepted(path)) {
366 |
367 | lineno = rb_tracearg_lineno(tp);
368 | file = RSTRING_PTR(path);
369 | line = FIX2INT(lineno);
370 |
371 | update_stack_size(context);
372 | print_event(tp, context);
373 |
374 | if(context->init_stack_size == -1) {
375 | context->stack_size = count_stack_size();
376 | context->init_stack_size = context->stack_size;
377 | }
378 |
379 | if (context->thread_pause) {
380 | context->stop_next = 1;
381 | context->dest_frame = -1;
382 | moved = 1;
383 | }
384 | else {
385 | moved = context->last_line != line || context->last_file == NULL ||
386 | strcmp(context->last_file, file) != 0;
387 | }
388 |
389 | if (context->dest_frame == -1 || context->calced_stack_size == context->dest_frame)
390 | {
391 | if (moved || !CTX_FL_TEST(context, CTX_FL_FORCE_MOVE))
392 | context->stop_next--;
393 | if (context->stop_next < 0)
394 | context->stop_next = -1;
395 | if (moved || (CTX_FL_TEST(context, CTX_FL_STEPPED) && !CTX_FL_TEST(context, CTX_FL_FORCE_MOVE)))
396 | {
397 | context->stop_line--;
398 | CTX_FL_UNSET(context, CTX_FL_STEPPED);
399 | }
400 | }
401 | else if(context->calced_stack_size < context->dest_frame)
402 | {
403 | context->stop_next = 0;
404 | }
405 | if(check_stop_frame(context))
406 | {
407 | context->stop_next = 0;
408 | context->stop_frame = -1;
409 | }
410 |
411 | breakpoint = breakpoint_find(breakpoints, path, lineno, trace_point);
412 | if (context->stop_next == 0 || context->stop_line == 0 || breakpoint != Qnil) {
413 | rb_ensure(start_inspector, context_object, stop_inspector, Qnil);
414 | if(context->stack_size <= context->init_stack_size && context->hit_user_code) {
415 | context->script_finished = 1;
416 | }
417 | if(!context->script_finished) {
418 | context->stop_reason = CTX_STOP_STEP;
419 | if (breakpoint != Qnil) {
420 | context->stop_reason = CTX_STOP_BREAKPOINT;
421 | rb_funcall(context_object, idAtBreakpoint, 1, breakpoint);
422 | }
423 | reset_stepping_stop_points(context);
424 | call_at_line(context, file, line, context_object);
425 | }
426 | }
427 | }
428 | cleanup(context);
429 | }
430 |
431 | static void
432 | process_return_event(VALUE trace_point, void *data)
433 | {
434 | VALUE context_object;
435 | debug_context_t *context;
436 |
437 | context_object = Debase_current_context(mDebase);
438 | Data_Get_Struct(context_object, debug_context_t, context);
439 | if (!check_start_processing(context, rb_thread_current())) return;
440 |
441 | --context->calced_stack_size;
442 | update_stack_size(context);
443 | /* it is important to check stop_frame after stack size updated
444 | if the order will be changed change Context_stop_frame accordingly.
445 | */
446 | if(check_stop_frame(context))
447 | {
448 | context->stop_next = 1;
449 | context->stop_frame = -1;
450 | }
451 |
452 | print_event(TRACE_POINT, context);
453 | cleanup(context);
454 | }
455 |
456 | static void
457 | process_call_event(VALUE trace_point, void *data)
458 | {
459 | VALUE context_object;
460 | debug_context_t *context;
461 |
462 | context_object = Debase_current_context(mDebase);
463 | Data_Get_Struct(context_object, debug_context_t, context);
464 | if (!check_start_processing(context, rb_thread_current())) return;
465 |
466 | ++context->calced_stack_size;
467 | update_stack_size(context);
468 | print_event(TRACE_POINT, context);
469 | cleanup(context);
470 | }
471 |
472 | static void
473 | process_raise_event(VALUE trace_point, void *data)
474 | {
475 | VALUE path;
476 | VALUE lineno;
477 | VALUE context_object;
478 | VALUE hit_count;
479 | VALUE exception_name;
480 | debug_context_t *context;
481 | rb_trace_point_t *tp;
482 | char *file;
483 | int line;
484 | int c_hit_count;
485 |
486 | context_object = Debase_current_context(mDebase);
487 | Data_Get_Struct(context_object, debug_context_t, context);
488 | if (!check_start_processing(context, rb_thread_current())) return;
489 |
490 | update_stack_size(context);
491 | tp = TRACE_POINT;
492 | if (catchpoint_hit_count(catchpoints, rb_tracearg_raised_exception(tp), &exception_name) != Qnil) {
493 | rb_ensure(start_inspector, context_object, stop_inspector, Qnil);
494 | path = rb_tracearg_path(tp);
495 |
496 | if (is_path_accepted(path) == Qtrue) {
497 | lineno = rb_tracearg_lineno(tp);
498 | file = RSTRING_PTR(path);
499 | line = FIX2INT(lineno);
500 | /* On 64-bit systems with gcc and -O2 there seems to be
501 | an optimization bug in running INT2FIX(FIX2INT...)..)
502 | So we do this in two steps.
503 | */
504 | c_hit_count = FIX2INT(rb_hash_aref(catchpoints, exception_name)) + 1;
505 | hit_count = INT2FIX(c_hit_count);
506 | rb_hash_aset(catchpoints, exception_name, hit_count);
507 | context->stop_reason = CTX_STOP_CATCHPOINT;
508 | rb_funcall(context_object, idAtCatchpoint, 1, rb_tracearg_raised_exception(tp));
509 | call_at_line(context, file, line, context_object);
510 | }
511 | }
512 |
513 | cleanup(context);
514 | }
515 |
516 | static VALUE
517 | Debase_setup_tracepoints(VALUE self)
518 | {
519 | if (started) return Qnil;
520 | started = 1;
521 |
522 | contexts = rb_hash_new();
523 | breakpoints = rb_ary_new();
524 | catchpoints = rb_hash_new();
525 |
526 | tpLine = rb_tracepoint_new(Qnil, RUBY_EVENT_LINE, process_line_event, NULL);
527 | rb_global_variable(&tpLine);
528 |
529 | tpReturn = rb_tracepoint_new(Qnil, RUBY_EVENT_RETURN | RUBY_EVENT_B_RETURN | RUBY_EVENT_C_RETURN | RUBY_EVENT_END,
530 | process_return_event, NULL);
531 | rb_global_variable(&tpReturn);
532 |
533 | tpCall = rb_tracepoint_new(Qnil, RUBY_EVENT_CALL | RUBY_EVENT_B_CALL | RUBY_EVENT_C_CALL | RUBY_EVENT_CLASS,
534 | process_call_event, NULL);
535 | rb_global_variable(&tpCall);
536 |
537 | tpRaise = rb_tracepoint_new(Qnil, RUBY_EVENT_RAISE, process_raise_event, NULL);
538 | rb_global_variable(&tpRaise);
539 |
540 | return Qnil;
541 | }
542 |
543 | static VALUE
544 | Debase_remove_tracepoints(VALUE self)
545 | {
546 | started = 0;
547 |
548 | if (tpLine != Qnil) rb_tracepoint_disable(tpLine);
549 | if (tpReturn != Qnil) rb_tracepoint_disable(tpReturn);
550 | if (tpCall != Qnil) rb_tracepoint_disable(tpCall);
551 | if (tpRaise != Qnil) rb_tracepoint_disable(tpRaise);
552 |
553 | return Qnil;
554 | }
555 |
556 | static VALUE
557 | debase_prepare_context(VALUE self, VALUE file, VALUE stop)
558 | {
559 | VALUE context_object;
560 | debug_context_t *context;
561 |
562 | context_object = Debase_current_context(self);
563 | Data_Get_Struct(context_object, debug_context_t, context);
564 |
565 | if(RTEST(stop)) {
566 | context->stop_next = 1;
567 | Debase_enable_trace_points(self);
568 | }
569 | ruby_script(RSTRING_PTR(file));
570 | return self;
571 | }
572 |
573 | static VALUE
574 | Debase_prepare_context(VALUE self)
575 | {
576 | Debase_current_context(self);
577 |
578 | return self;
579 | }
580 |
581 | static VALUE
582 | Debase_debug_load(int argc, VALUE *argv, VALUE self)
583 | {
584 | VALUE file, stop, increment_start;
585 | int state;
586 |
587 | if(rb_scan_args(argc, argv, "12", &file, &stop, &increment_start) == 1)
588 | {
589 | stop = Qfalse;
590 | increment_start = Qtrue;
591 | }
592 | Debase_setup_tracepoints(self);
593 | debase_prepare_context(self, file, stop);
594 | rb_load_protect(file, 0, &state);
595 | if (0 != state)
596 | {
597 | VALUE error_info = rb_errinfo();
598 | rb_set_errinfo(Qnil);
599 | return error_info;
600 | }
601 | return Qnil;
602 | }
603 |
604 | static int
605 | values_i(VALUE key, VALUE value, VALUE ary)
606 | {
607 | rb_ary_push(ary, value);
608 | return ST_CONTINUE;
609 | }
610 |
611 | static VALUE
612 | Debase_contexts(VALUE self)
613 | {
614 | VALUE ary;
615 |
616 | ary = rb_ary_new();
617 | /* check that all contexts point to alive threads */
618 | rb_hash_foreach(contexts, remove_dead_threads, 0);
619 |
620 | rb_hash_foreach(contexts, values_i, ary);
621 |
622 | return ary;
623 | }
624 |
625 | static VALUE
626 | Debase_breakpoints(VALUE self)
627 | {
628 | return breakpoints;
629 | }
630 |
631 | static VALUE
632 | Debase_catchpoints(VALUE self)
633 | {
634 | if (catchpoints == Qnil)
635 | rb_raise(rb_eRuntimeError, "Debugger.start is not called yet.");
636 | return catchpoints;
637 | }
638 |
639 | static VALUE
640 | Debase_started(VALUE self)
641 | {
642 | return started ? Qtrue : Qfalse;
643 | }
644 |
645 | /*
646 | * call-seq:
647 | * Debase.verbose? -> bool
648 | *
649 | * Returns +true+ if verbose output of TracePoint API events is enabled.
650 | */
651 | static VALUE
652 | Debase_verbose(VALUE self)
653 | {
654 | return verbose;
655 | }
656 |
657 | /*
658 | * call-seq:
659 | * Debase.verbose = bool
660 | *
661 | * Enable verbose output of every TracePoint API events, useful for debugging debase.
662 | */
663 | static VALUE
664 | Debase_set_verbose(VALUE self, VALUE value)
665 | {
666 | verbose = RTEST(value) ? Qtrue : Qfalse;
667 | return value;
668 | }
669 |
670 | /*
671 | * call-seq:
672 | * Debase.enable_file_filtering(bool)
673 | *
674 | * Enables/disables file filtering.
675 | */
676 | static VALUE
677 | Debase_enable_file_filtering(VALUE self, VALUE value)
678 | {
679 | file_filter_enabled = RTEST(value) ? Qtrue : Qfalse;
680 | return value;
681 | }
682 |
683 | #if RUBY_API_VERSION_CODE >= 20500 && RUBY_API_VERSION_CODE < 20600 && !(RUBY_RELEASE_YEAR == 2017 && RUBY_RELEASE_MONTH == 10 && RUBY_RELEASE_DAY == 10)
684 | static const rb_iseq_t *
685 | my_iseqw_check(VALUE iseqw)
686 | {
687 | rb_iseq_t *iseq = DATA_PTR(iseqw);
688 |
689 | if (!iseq->body) {
690 | return NULL;
691 | }
692 |
693 | return iseq;
694 | }
695 |
696 | static VALUE
697 | Debase_set_trace_flag_to_iseq(VALUE self, VALUE rb_iseq) {
698 | if (!SPECIAL_CONST_P(rb_iseq) && RBASIC_CLASS(rb_iseq) == rb_cISeq) {
699 | const rb_iseq_t *iseq = my_iseqw_check(rb_iseq);
700 |
701 | if(iseq) {
702 | rb_iseq_trace_set(iseq, RUBY_EVENT_TRACEPOINT_ALL);
703 | }
704 | }
705 | }
706 |
707 | static VALUE
708 | Debase_unset_trace_flags(VALUE self, VALUE rb_iseq) {
709 | if (!SPECIAL_CONST_P(rb_iseq) && RBASIC_CLASS(rb_iseq) == rb_cISeq) {
710 | const rb_iseq_t *iseq = my_iseqw_check(rb_iseq);
711 |
712 | if(iseq) {
713 | rb_iseq_trace_set(iseq, RUBY_EVENT_NONE);
714 | }
715 | }
716 | }
717 | #else
718 | static VALUE
719 | Debase_set_trace_flag_to_iseq(VALUE self, VALUE rb_iseq) {
720 | return Qnil;
721 | }
722 |
723 | static VALUE
724 | Debase_unset_trace_flags(VALUE self, VALUE rb_iseq) {
725 | return Qnil;
726 | }
727 | #endif
728 |
729 | static VALUE
730 | Debase_init_variables()
731 | {
732 | started = 0;
733 | verbose = Qfalse;
734 | locker = Qnil;
735 | file_filter_enabled = Qfalse;
736 | contexts = Qnil;
737 | catchpoints = Qnil;
738 | breakpoints = Qnil;
739 |
740 | context_init_variables();
741 | breakpoint_init_variables();
742 |
743 | return Qtrue;
744 | }
745 |
746 | /*
747 | * Document-class: Debase
748 | *
749 | * == Summary
750 | *
751 | * This is a singleton class allows controlling the debugger. Use it to start/stop debugger,
752 | * set/remove breakpoints, etc.
753 | */
754 | void
755 | Init_debase_internals()
756 | {
757 | mDebase = rb_define_module("Debase");
758 | rb_define_module_function(mDebase, "setup_tracepoints", Debase_setup_tracepoints, 0);
759 | rb_define_module_function(mDebase, "remove_tracepoints", Debase_remove_tracepoints, 0);
760 | rb_define_module_function(mDebase, "current_context", Debase_current_context, 0);
761 | rb_define_module_function(mDebase, "debug_load", Debase_debug_load, -1);
762 | rb_define_module_function(mDebase, "contexts", Debase_contexts, 0);
763 | rb_define_module_function(mDebase, "breakpoints", Debase_breakpoints, 0);
764 | rb_define_module_function(mDebase, "catchpoints", Debase_catchpoints, 0);
765 | rb_define_module_function(mDebase, "started?", Debase_started, 0);
766 | rb_define_module_function(mDebase, "verbose?", Debase_verbose, 0);
767 | rb_define_module_function(mDebase, "verbose=", Debase_set_verbose, 1);
768 | rb_define_module_function(mDebase, "enable_file_filtering", Debase_enable_file_filtering, 1);
769 | rb_define_module_function(mDebase, "enable_trace_points", Debase_enable_trace_points, 0);
770 | rb_define_module_function(mDebase, "prepare_context", Debase_prepare_context, 0);
771 | rb_define_module_function(mDebase, "init_variables", Debase_init_variables, 0);
772 | rb_define_module_function(mDebase, "set_trace_flag_to_iseq", Debase_set_trace_flag_to_iseq, 1);
773 |
774 | //use only for tests
775 | rb_define_module_function(mDebase, "unset_iseq_flags", Debase_unset_trace_flags, 1);
776 |
777 | idAlive = rb_intern("alive?");
778 | idAtLine = rb_intern("at_line");
779 | idAtBreakpoint = rb_intern("at_breakpoint");
780 | idAtCatchpoint = rb_intern("at_catchpoint");
781 | idFileFilter = rb_intern("file_filter");
782 | idAccept = rb_intern("accept?");
783 |
784 | cContext = Init_context(mDebase);
785 | Init_breakpoint(mDebase);
786 | cDebugThread = rb_define_class_under(mDebase, "DebugThread", rb_cThread);
787 | Debase_init_variables();
788 |
789 | rb_global_variable(&locker);
790 | rb_global_variable(&breakpoints);
791 | rb_global_variable(&catchpoints);
792 | rb_global_variable(&contexts);
793 | }
794 |
--------------------------------------------------------------------------------
/ext/debase_internals.h:
--------------------------------------------------------------------------------
1 | #ifndef DEBASE_INTERNALS
2 | #define DEBASE_INTERNALS
3 |
4 | #include "ruby.h"
5 | #include "ruby/debug.h"
6 |
7 | #include
8 | #if RUBY_API_VERSION_CODE == 20500
9 | #include
10 | #include
11 | #endif
12 |
13 | #if RUBY_API_VERSION_CODE >= 30000
14 | #include
15 | #endif
16 |
17 | typedef struct rb_trace_arg_struct rb_trace_point_t;
18 |
19 | /* Debase::Context */
20 | /* flags */
21 | #define CTX_FL_SUSPEND (1<<1)
22 | #define CTX_FL_TRACING (1<<2)
23 | #define CTX_FL_SKIPPED (1<<3)
24 | #define CTX_FL_IGNORE (1<<4)
25 | #define CTX_FL_DEAD (1<<5)
26 | #define CTX_FL_WAS_RUNNING (1<<6)
27 | #define CTX_FL_ENABLE_BKPT (1<<7)
28 | #define CTX_FL_STEPPED (1<<8)
29 | #define CTX_FL_FORCE_MOVE (1<<9)
30 | #define CTX_FL_CATCHING (1<<10)
31 | #define CTX_FL_UPDATE_STACK (1<<11)
32 |
33 | /* macro functions */
34 | #define CTX_FL_TEST(c,f) ((c)->flags & (f))
35 | #define CTX_FL_SET(c,f) do { (c)->flags |= (f); } while (0)
36 | #define CTX_FL_UNSET(c,f) do { (c)->flags &= ~(f); } while (0)
37 |
38 | #define IS_THREAD_ALIVE(t) (rb_funcall((t), idAlive, 0) == Qtrue)
39 | #define TRACE_POINT (rb_tracearg_from_tracepoint(trace_point))
40 |
41 | /* types */
42 | typedef enum {CTX_STOP_NONE, CTX_STOP_STEP, CTX_STOP_BREAKPOINT, CTX_STOP_CATCHPOINT} ctx_stop_reason;
43 |
44 | typedef struct debug_frame_t
45 | {
46 | struct debug_frame_t *prev;
47 | const char *file;
48 | int line;
49 | VALUE binding;
50 | VALUE self;
51 | } debug_frame_t;
52 |
53 | typedef struct debug_context {
54 | debug_frame_t *stack;
55 | int stack_size;
56 |
57 | VALUE thread;
58 | int thnum;
59 | int flags;
60 |
61 | ctx_stop_reason stop_reason;
62 | int stop_next;
63 | int stop_line;
64 | int stop_frame;
65 | int thread_pause;
66 |
67 | /* dest_frame uses calced_stack_size for stepping */
68 | int dest_frame;
69 | int calced_stack_size;
70 |
71 | char *last_file;
72 | int last_line;
73 | int init_stack_size;
74 | int script_finished;
75 | int hit_user_code;
76 | } debug_context_t;
77 |
78 | typedef struct
79 | {
80 | debug_context_t *context;
81 | VALUE context_object;
82 | VALUE path;
83 | VALUE lineno;
84 | } inspector_data_t;
85 |
86 | /* functions */
87 | extern VALUE Init_context(VALUE mDebase);
88 | extern VALUE context_create(VALUE thread, VALUE cDebugThread);
89 | extern void reset_stepping_stop_points(debug_context_t *context);
90 | extern VALUE Context_ignored(VALUE self);
91 | extern void fill_stack(debug_context_t *context, const rb_debug_inspector_t *inspector);
92 | extern void clear_stack(debug_context_t *context);
93 |
94 | /* locked threads container */
95 | /* types */
96 | typedef struct locked_thread_t {
97 | VALUE thread;
98 | struct locked_thread_t *next;
99 | } locked_thread_t;
100 |
101 | /* functions */
102 | extern int is_in_locked(VALUE thread_id);
103 | extern void add_to_locked(VALUE thread);
104 | extern VALUE remove_from_locked();
105 | extern VALUE enable_trace_points();
106 |
107 | /* breakpoints and catchpoints */
108 | /* types */
109 | typedef struct
110 | {
111 | VALUE enabled;
112 | VALUE source;
113 | VALUE expr;
114 | int line;
115 | int id;
116 | } breakpoint_t;
117 |
118 | extern VALUE catchpoint_hit_count(VALUE catchpoints, VALUE exception, VALUE *exception_name);
119 | extern VALUE breakpoint_find(VALUE breakpoints, VALUE source, VALUE pos, VALUE trace_point);
120 | extern void Init_breakpoint(VALUE mDebase);
121 |
122 | extern void breakpoint_init_variables();
123 | extern void context_init_variables();
124 | #endif
125 |
--------------------------------------------------------------------------------
/ext/extconf.rb:
--------------------------------------------------------------------------------
1 | if defined?(RUBY_ENGINE) && 'rbx' == RUBY_ENGINE
2 | # create dummy Makefile to indicate success
3 | f = File.open(File.join(File.dirname(__FILE__), "Makefile"), "w")
4 | f.write("all:\n\techo all\ninstall:\n\techo installed\n")
5 | f.close
6 | return
7 | end
8 |
9 | # autodetect ruby headers
10 | unless ARGV.any? {|arg| arg.include?('--with-ruby-include') }
11 | require 'rbconfig'
12 | bindir = RbConfig::CONFIG['bindir']
13 | if bindir =~ %r{(^.*/\.rbenv/versions)/([^/]+)/bin$}
14 | ruby_include = "#{$1}/#{$2}/include/ruby-1.9.1/ruby-#{$2}"
15 | ruby_include = "#{ENV['RBENV_ROOT']}/sources/#{$2}/ruby-#{$2}" unless File.exist?(ruby_include)
16 | ARGV << "--with-ruby-include=#{ruby_include}"
17 | elsif bindir =~ %r{(^.*/\.rvm/rubies)/([^/]+)/bin$}
18 | ruby_include = "#{$1}/#{$2}/include/ruby-1.9.1/#{$2}"
19 | ruby_include = "#{ENV['rvm_path']}/src/#{$2}" unless File.exist?(ruby_include)
20 | ARGV << "--with-ruby-include=#{ruby_include}"
21 | end
22 | end
23 |
24 | require "mkmf"
25 |
26 | RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
27 |
28 | require "debase/ruby_core_source"
29 |
30 | hdrs = proc {
31 | have_header("vm_core.h") and
32 | (have_header("iseq.h") or have_header("iseq.h", ["vm_core.h"])) and
33 | have_header("version.h")
34 | }
35 |
36 | # Allow use customization of compile options. For example, the
37 | # following lines could be put in config_options to to turn off
38 | # optimization:
39 | # $CFLAGS='-fPIC -fno-strict-aliasing -g3 -ggdb -O2 -fPIC'
40 | config_file = File.join(File.dirname(__FILE__), 'config_options.rb')
41 | load config_file if File.exist?(config_file)
42 | cflags = [$CFLAGS]
43 | cflags.push(*%w[
44 | -Werror=implicit-function-declaration
45 | -Werror=discarded-qualifiers
46 | -Werror=incompatible-pointer-types
47 | ])
48 |
49 | if RbConfig::MAKEFILE_CONFIG['CC'].include?('clang')
50 | cflags.push(*%w[
51 | -Werror=incompatible-function-pointer-types
52 | ])
53 | end
54 |
55 |
56 | if ENV['debase_debug']
57 | cflags.push(*%w[
58 | -Wall
59 | -Werror
60 | -g3
61 | ])
62 | end
63 |
64 | $CFLAGS += ' ' + cflags.join(' ')
65 |
66 |
67 | dir_config("ruby")
68 | if !Debase::RubyCoreSource.create_makefile_with_core(hdrs, "debase_internals")
69 | STDERR.print("Makefile creation failed\n")
70 | STDERR.print("*************************************************************\n\n")
71 | STDERR.print(" NOTE: If your headers were not found, try passing\n")
72 | STDERR.print(" --with-ruby-include=PATH_TO_HEADERS \n\n")
73 | STDERR.print("*************************************************************\n\n")
74 | exit(1)
75 | end
76 |
--------------------------------------------------------------------------------
/ext/hacks.h:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current()))
5 |
6 | #if RUBY_API_VERSION_CODE >= 20500
7 | #if (RUBY_RELEASE_YEAR == 2017 && RUBY_RELEASE_MONTH == 10 && RUBY_RELEASE_DAY == 10) //workaround for 2.5.0-preview1
8 | #define TH_CFP(thread) ((rb_control_frame_t *)(thread)->ec.cfp)
9 | #define TH_INFO(thread) ((rb_thread_t *)thread)
10 | #else
11 | #define TH_CFP(thread) ((rb_control_frame_t *)(thread)->ec->cfp)
12 | #define TH_INFO(thread) ((rb_execution_context_t *)(thread)->ec)
13 | #endif
14 | #else
15 | #define TH_CFP(thread) ((rb_control_frame_t *)(thread)->cfp)
16 | #define TH_INFO(thread) ((rb_thread_t *)thread)
17 | #endif
18 |
19 | extern void
20 | update_stack_size(debug_context_t *context)
21 | {
22 | rb_thread_t *thread;
23 |
24 | thread = ruby_current_thread;
25 | /* see backtrace_each in vm_backtrace.c */
26 | context->stack_size = (int)(RUBY_VM_END_CONTROL_FRAME(TH_INFO(thread)) - TH_CFP(thread) - 1);
27 | if (CTX_FL_TEST(context, CTX_FL_UPDATE_STACK)) {
28 | context->calced_stack_size = context->stack_size;
29 | CTX_FL_UNSET(context, CTX_FL_UPDATE_STACK);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/ext/locker.c:
--------------------------------------------------------------------------------
1 | #include
2 | static locked_thread_t *locked_head = NULL;
3 | static locked_thread_t *locked_tail = NULL;
4 |
5 | extern int
6 | is_in_locked(VALUE thread)
7 | {
8 | locked_thread_t *node;
9 |
10 | if(!locked_head) return 0;
11 |
12 | for(node = locked_head; node != locked_tail; node = node->next)
13 | {
14 | if(node->thread == thread) return 1;
15 | }
16 | return 0;
17 | }
18 |
19 | extern void
20 | add_to_locked(VALUE thread)
21 | {
22 | locked_thread_t *node;
23 |
24 | if(is_in_locked(thread)) return;
25 |
26 | node = ALLOC(locked_thread_t);
27 | node->thread = thread;
28 | node->next = NULL;
29 | if(locked_tail)
30 | locked_tail->next = node;
31 | locked_tail = node;
32 | if(!locked_head)
33 | locked_head = node;
34 | }
35 |
36 | extern VALUE
37 | remove_from_locked()
38 | {
39 | VALUE thread;
40 | locked_thread_t *node;
41 |
42 | if(locked_head == NULL)
43 | return Qnil;
44 |
45 | node = locked_head;
46 | locked_head = locked_head->next;
47 | if(locked_tail == node)
48 | locked_tail = NULL;
49 | thread = node->thread;
50 | xfree(node);
51 | return thread;
52 | }
53 |
--------------------------------------------------------------------------------
/lib/debase.rb:
--------------------------------------------------------------------------------
1 | if defined?(RUBY_ENGINE) && 'rbx' == RUBY_ENGINE
2 | require 'debase/rbx'
3 | else
4 | require "debase_internals"
5 | end
6 | require "debase/version"
7 | require "debase/context"
8 |
9 | module Debase
10 | class << self
11 | attr_accessor :handler
12 |
13 | alias start_ setup_tracepoints
14 | alias stop remove_tracepoints
15 |
16 | # possibly deprecated options
17 | attr_accessor :keep_frame_binding, :tracing
18 |
19 | def start(options={}, &block)
20 | Debugger.const_set('ARGV', ARGV.clone) unless defined? Debugger::ARGV
21 | Debugger.const_set('PROG_SCRIPT', $0) unless defined? Debugger::PROG_SCRIPT
22 | Debugger.const_set('INITIAL_DIR', Dir.pwd) unless defined? Debugger::INITIAL_DIR
23 |
24 | monkey_patch_prepend
25 |
26 | Debugger.started? ? block && block.call(self) : Debugger.start_(&block)
27 | end
28 |
29 | def monkey_patch_prepend
30 | class << RubyVM::InstructionSequence
31 | def self.prepend(mod, *smth)
32 | super
33 | if mod.to_s.include?('Bootsnap') && RUBY_VERSION >= '2.5' && RUBY_VERSION < '2.6'
34 | prepend InstructionSequenceMixin
35 | end
36 | end
37 | end
38 | end
39 |
40 | # @param [String] file
41 | # @param [Fixnum] line
42 | # @param [String] expr
43 | def add_breakpoint(file, line, expr=nil)
44 | breakpoint = Breakpoint.new(file, line, expr)
45 | breakpoints << breakpoint
46 | enable_trace_points
47 | breakpoint
48 | end
49 |
50 | def remove_breakpoint(id)
51 | Breakpoint.remove breakpoints, id
52 | end
53 |
54 | def source_reload; {} end
55 |
56 | def post_mortem?
57 | false
58 | end
59 |
60 | def debug
61 | false
62 | end
63 |
64 | def add_catchpoint(exception)
65 | catchpoints[exception] = 0
66 | enable_trace_points
67 | end
68 |
69 | def remove_catchpoint(exception)
70 | catchpoints.delete(exception)
71 | end
72 |
73 | def clear_catchpoints
74 | catchpoints.clear
75 | end
76 |
77 | #call-seq:
78 | # Debase.skip { block } -> obj or nil
79 | #
80 | #The code inside of the block is escaped from the debugger.
81 | def skip
82 | #it looks like no-op is ok for this method for now
83 | #no-op
84 | end
85 |
86 | #call-seq:
87 | # Debugger.last_interrupted -> context
88 | #
89 | #Returns last debugged context.
90 | def last_context
91 | # not sure why we need this so let's return nil for now ;)
92 | nil
93 | end
94 |
95 | def file_filter
96 | @file_filter ||= FileFilter.new
97 | end
98 |
99 | module InstructionSequenceMixin
100 | def load_iseq(path)
101 | iseq = super(path)
102 |
103 | do_set_flags(iseq)
104 |
105 | iseq
106 | end
107 |
108 | def do_set_flags(iseq)
109 | Debugger.set_trace_flag_to_iseq(iseq)
110 | iseq.each_child { |child_iseq| do_set_flags(child_iseq) } if iseq.respond_to? :each_child
111 | end
112 | end
113 | end
114 |
115 | class FileFilter
116 | def initialize
117 | @enabled = false
118 | end
119 |
120 | def include(file_path)
121 | included << file_path unless excluded.delete(file_path)
122 | end
123 |
124 | def exclude(file_path)
125 | excluded << file_path unless included.delete(file_path)
126 | end
127 |
128 | def enable
129 | @enabled = true
130 | Debase.enable_file_filtering(@enabled);
131 | end
132 |
133 | def disable
134 | @enabled = false
135 | Debase.enable_file_filtering(@enabled);
136 | end
137 |
138 | def accept?(file_path)
139 | return true unless @enabled
140 | return false if file_path.nil?
141 | included.any? { |path| file_path.start_with?(path) } && excluded.all? { |path| !file_path.start_with?(path)}
142 | end
143 |
144 | private
145 | def included
146 | @included ||= []
147 | end
148 |
149 | def excluded
150 | @excluded ||= []
151 | end
152 | end
153 |
154 | class DebugThread < Thread
155 | def self.inherited
156 | raise RuntimeError.new("Can't inherit Debugger::DebugThread class")
157 | end
158 | end
159 | end
160 |
161 | Debugger = Debase
162 |
--------------------------------------------------------------------------------
/lib/debase/context.rb:
--------------------------------------------------------------------------------
1 | module Debase
2 | class Context
3 | def frame_locals(frame_no=0)
4 | frame_binding(frame_no).eval('local_variables.inject({}){|__h, __v| __h[__v.to_s] = eval(__v.to_s); __h}')
5 | rescue => e
6 | {'debase-debug' => "*Evaluation error: '#{e}'" }
7 | end
8 |
9 | def frame_class(frame_no=0)
10 | frame_self(frame_no).class
11 | end
12 |
13 | def frame_args_info(frame_no=0)
14 | nil
15 | end
16 |
17 | def handler
18 | Debase.handler or raise "No interface loaded"
19 | end
20 |
21 | def at_breakpoint(breakpoint)
22 | handler.at_breakpoint(self, breakpoint)
23 | end
24 |
25 | def at_catchpoint(excpt)
26 | handler.at_catchpoint(self, excpt)
27 | end
28 |
29 | def at_tracing(file, line)
30 | @tracing_started = true if File.identical?(file, File.join(Debugger::INITIAL_DIR, Debugger::PROG_SCRIPT))
31 | handler.at_tracing(self, file, line) if @tracing_started
32 | end
33 |
34 | def at_line(file, line)
35 | handler.at_line(self, file, line)
36 | end
37 |
38 | def at_return(file, line)
39 | handler.at_return(self, file, line)
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/debase/rbx.rb:
--------------------------------------------------------------------------------
1 | require 'debase/rbx/breakpoint'
2 | require 'debase/rbx/context'
3 | require 'debase/rbx/monkey'
4 |
5 | module Debase
6 | class << self
7 | attr_reader :breakpoints, :current_context
8 |
9 | def setup_tracepoints
10 | return nil unless @catchpoints.nil?
11 | @contexts = {}
12 | @catchpoints = {}
13 | @breakpoints = []
14 | Rubinius::CodeLoader.compiled_hook.add proc { |script|
15 | @breakpoints.each { | b |
16 | if b.source == script.file_path
17 | exec, ip = script.compiled_code.locate_line(b.pos)
18 | if exec
19 | exec.set_breakpoint ip, b
20 | end
21 | end
22 | }
23 | }
24 | spinup_thread
25 | Thread.current.set_debugger_thread @thread
26 | self
27 | end
28 |
29 | def remove_tracepoints
30 | end
31 |
32 | def prepare_context(thread=Thread.current)
33 | cleanup_contexts
34 | @contexts[thread] ||= Context.new(thread)
35 | end
36 |
37 | def cleanup_contexts
38 | @contexts.delete_if { |key, _| !key.alive? }
39 | end
40 |
41 | def contexts
42 | @contexts.values
43 | end
44 |
45 | def debug_load(file, stop=false, incremental_start=true)
46 | setup_tracepoints
47 | prepare_context
48 | begin
49 | load file
50 | nil
51 | rescue Exception => e
52 | e
53 | end
54 | end
55 |
56 | def started?
57 | !@thread.nil?
58 | end
59 |
60 | def listen
61 | channel = nil
62 | while true
63 | channel << true if channel
64 | # Wait for someone to stop
65 | bp, thread, channel, locs = @local_channel.receive
66 |
67 | if bp
68 | context = prepare_context(thread)
69 | @current_context = context
70 | context.fill_frame_info(locs)
71 | context.at_breakpoint(bp)
72 | context.at_line(locs.first.file, locs.first.line)
73 | puts "resuming execution"
74 | context.clear_frame_info
75 | @current_context = nil
76 | end
77 | end
78 | end
79 |
80 | def spinup_thread
81 | return if @thread
82 |
83 | @local_channel = Rubinius::Channel.new
84 |
85 | @thread = DebugThread.new do
86 | begin
87 | listen
88 | rescue Exception => e
89 | puts e
90 | e.render("Listening")
91 | end
92 | end
93 |
94 | @thread.setup_control!(@local_channel)
95 | end
96 | end
97 |
98 | class DebugThread < Thread
99 | def set_debugger_thread
100 | end
101 | end
102 | end
103 |
--------------------------------------------------------------------------------
/lib/debase/rbx/breakpoint.rb:
--------------------------------------------------------------------------------
1 | module Debase
2 | class Breakpoint
3 | @@global_id = 1
4 |
5 | attr_accessor :source, :pos, :expr, :enabled
6 | attr_reader :id
7 |
8 | def initialize(file, line, expr=nil)
9 | @source = file
10 | @pos = line
11 | @expr = expr
12 | @id = @@global_id
13 | @@global_id = @@global_id + 1
14 | end
15 |
16 | def delete!
17 |
18 | end
19 |
20 | def self.remove(breakpoints, id)
21 | bp = breakpoints.delete_if {|b| b.id == id}
22 | bp.delete! if bp
23 | bp
24 | end
25 | end
26 | end
--------------------------------------------------------------------------------
/lib/debase/rbx/context.rb:
--------------------------------------------------------------------------------
1 | require 'debase/rbx/frame'
2 |
3 | module Debase
4 | class Context
5 | @@max_thread_num = 1
6 | attr_reader :thread, :thnum
7 |
8 | def initialize(thread)
9 | @thread = thread
10 | @thnum = @@max_thread_num
11 | @@max_thread_num = @@max_thread_num + 1
12 | end
13 |
14 | def frame_file(frame=0)
15 | @frames[frame].file
16 | end
17 |
18 | def frame_line(frame=0)
19 | @frames[frame].line
20 | end
21 |
22 | def frame_binding(frame=0)
23 | @frames[frame].binding
24 | end
25 |
26 | def frame_self(frame=0)
27 | @frames[frame].self
28 | end
29 |
30 | def stack_size
31 | @frames.size
32 | end
33 |
34 | def fill_frame_info(locations)
35 | @frames = []
36 | locations.each do |loc|
37 | @frames << Frame.new(loc)
38 | end
39 | @frames
40 | end
41 |
42 | def clear_frame_info
43 | @frames = nil
44 | end
45 |
46 | def stop_reason
47 | :breakpoint
48 | end
49 |
50 | def ignored?
51 | thread.is_a? DebugThread
52 | end
53 |
54 | def dead?
55 | !@thread.alive?
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/lib/debase/rbx/frame.rb:
--------------------------------------------------------------------------------
1 | module Debase
2 | class Frame
3 | def initialize(location)
4 | @location = location
5 | end
6 |
7 | def binding
8 | @binding ||= Binding.setup(@location.variables, @location.method, @location.constant_scope)
9 | end
10 |
11 | def line
12 | @location.line
13 | end
14 |
15 | def file
16 | @location.file
17 | end
18 |
19 | def self
20 | @location.receiver
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/debase/rbx/monkey.rb:
--------------------------------------------------------------------------------
1 | class Object
2 | # todo: remove me, I'm here only for debugging
3 | def method_missing(name, *args)
4 | puts "#{self.class}.#{name}"
5 | super(name, args)
6 | end
7 | end
8 |
9 | module Rubinius
10 | class Location
11 | attr_reader :receiver
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/debase/version.rb:
--------------------------------------------------------------------------------
1 | module Debase
2 | VERSION = "0.2.9" unless defined? VERSION
3 | end
4 |
--------------------------------------------------------------------------------
/test/example/a/example.rb:
--------------------------------------------------------------------------------
1 | puts "a"
2 |
--------------------------------------------------------------------------------
/test/example/at-exit.rb:
--------------------------------------------------------------------------------
1 | at_exit do
2 | puts "Hello world!"
3 | end
4 |
--------------------------------------------------------------------------------
/test/example/b/example.rb:
--------------------------------------------------------------------------------
1 | puts "b"
2 |
--------------------------------------------------------------------------------
/test/example/bootsnap/a.rb:
--------------------------------------------------------------------------------
1 | class A
2 | def foo(a)
3 | puts a
4 | end
5 | end
--------------------------------------------------------------------------------
/test/example/bootsnap/bootsnap.rb:
--------------------------------------------------------------------------------
1 | require_relative 'a.rb'
2 |
3 | A.new.foo('hi')
--------------------------------------------------------------------------------
/test/example/bp_loop_issue.rb:
--------------------------------------------------------------------------------
1 | 1.upto(2) {
2 | sleep 0.01
3 | }
4 |
--------------------------------------------------------------------------------
/test/example/breakpoints-basename.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../a/example.rb", __FILE__)
2 | require File.expand_path("../b/example.rb", __FILE__)
3 |
--------------------------------------------------------------------------------
/test/example/brkpt-class-bug.rb:
--------------------------------------------------------------------------------
1 | class A
2 | def A.show
3 | puts "hi"
4 | end
5 | end
6 | x = 1
7 | A.show
8 | y = 2
9 |
--------------------------------------------------------------------------------
/test/example/classes.rb:
--------------------------------------------------------------------------------
1 | class Mine
2 | def initialize
3 | @myvar = 'init'
4 | end
5 | def mymethod(a, b=5, &block)
6 | end
7 | def self.classmeth
8 | end
9 | end
10 | me = Mine.new
11 | metoo = Mine(new)
12 |
--------------------------------------------------------------------------------
/test/example/dollar-0.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # A test to see that rdebug set's $0 properly.
3 | puts $0
4 | puts $PROGRAM_NAME
5 | puts __FILE__
6 |
7 |
--------------------------------------------------------------------------------
/test/example/except-bug1.rb:
--------------------------------------------------------------------------------
1 | def foo(n)
2 | 1/0
3 | end
4 | foo(5)
5 |
--------------------------------------------------------------------------------
/test/example/file with space.rb:
--------------------------------------------------------------------------------
1 | puts 'Ha!'
2 |
--------------------------------------------------------------------------------
/test/example/gcd.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # GCD. We assume positive numbers
4 | def gcd(a, b)
5 | # Make: a <= b
6 | if a > b
7 | a, b = [b, a]
8 | end
9 |
10 | return nil if a <= 0
11 |
12 | if a == 1 or b-a == 0
13 | return a
14 | end
15 | return gcd(b-a, a)
16 | end
17 |
18 | gcd(3,5)
19 |
--------------------------------------------------------------------------------
/test/example/info-var-bug.rb:
--------------------------------------------------------------------------------
1 | class Lousy_inspect
2 | attr_accessor :var
3 | def inspect # An unhelpful inspect
4 | throw "Foo" # Raises an exception
5 | end
6 | def initialize
7 | @var = 'initialized'
8 | end
9 | end
10 | class Lousy_inspect_and_to_s
11 | attr_accessor :var
12 | def inspect # An unhelpful inspect
13 | throw "Foo" # Raises an exception
14 | end
15 | def to_s # An unhelpful to_s
16 | throw "bar" # Raises an exception
17 | end
18 | def initialize
19 | @var = 'initialized' # Something to inspect
20 | end
21 | end
22 |
23 | # Something that will be passed objects with
24 | # bad inspect or to_s methods
25 | class UnsuspectingClass
26 | @@Const = 'A constant'
27 | @@var = 'a class variable'
28 | def initialize(a)
29 | @a = a # "info locals" will try to use
30 | # inspect or to_s here
31 | @b = 5
32 | end
33 | end
34 | def test_Lousy_inspect
35 | x = Lousy_inspect.new
36 | return x
37 | end
38 | def test_lousy_inspect_and_to_s
39 | x = Lousy_inspect_and_to_s.new
40 | return x
41 | end
42 | x = test_Lousy_inspect
43 | y = test_lousy_inspect_and_to_s
44 | UnsuspectingClass.new(10)
45 | UnsuspectingClass.new(x)
46 | UnsuspectingClass.new(y)
47 | y = 2
48 |
--------------------------------------------------------------------------------
/test/example/info-var-bug2.rb:
--------------------------------------------------------------------------------
1 | s = '<%= PRODUCT[:name] %>'
2 | y = 0
3 |
--------------------------------------------------------------------------------
/test/example/null.rb:
--------------------------------------------------------------------------------
1 | # Nothing here. Move along.
2 |
--------------------------------------------------------------------------------
/test/example/output.rb:
--------------------------------------------------------------------------------
1 | puts "one"
2 | puts "two"
3 |
--------------------------------------------------------------------------------
/test/example/pm-bug.rb:
--------------------------------------------------------------------------------
1 | a = 1
2 | @x = 2
3 | raise
4 |
--------------------------------------------------------------------------------
/test/example/pm.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Test Debugger.catchpoint and post-mortem handling
3 | def zero_div
4 | x = 5
5 | 1/0
6 | end
7 | x = 2
8 | zero_div
9 | raise RuntimeError
10 | x = 3
11 |
12 |
--------------------------------------------------------------------------------
/test/example/raise.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | raise "abc"
4 |
--------------------------------------------------------------------------------
/test/helper.rb:
--------------------------------------------------------------------------------
1 | require "test-unit"
2 | require "debase"
--------------------------------------------------------------------------------
/test/test_base.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require File.expand_path("helper", File.dirname(__FILE__))
3 |
4 | # Some tests of Debugger module in C extension ruby_debug
5 | class TestRubyDebug < Test::Unit::TestCase
6 | def test_version
7 | assert(defined?(Debugger::VERSION))
8 | end
9 |
10 | # test current_context
11 | def test_current_context
12 | assert_equal(false, Debugger.started?,
13 | 'debugger should not initially be started.')
14 | Debugger.start_
15 | # we need to add the breakpoint to force enabling trace points
16 | Debugger.add_breakpoint(__FILE__, 1)
17 | assert(Debugger.started?,
18 | 'debugger should now be started.')
19 | # assert_equal(__LINE__, Debugger.current_context.frame_line)
20 | # assert_equal(nil, Debugger.current_context.frame_args_info,
21 | # 'no frame args info.')
22 | # assert_equal(Debugger.current_context.frame_file,
23 | # Debugger.current_context.frame_file(0))
24 | # assert_equal(File.basename(__FILE__),
25 | # File.basename(Debugger.current_context.frame_file))
26 | # assert_raises(ArgumentError) {Debugger.current_context.frame_file(1, 2)}
27 | # assert_raises(ArgumentError) {Debugger.current_context.frame_file(15)}
28 | # assert_equal(19, Debugger.current_context.stack_size)
29 | # assert_equal(TestRubyDebug, Debugger.current_context.frame_class)
30 | assert_equal(false, Debugger.current_context.dead?, 'Not dead yet!')
31 | ensure
32 | Debugger.stop
33 | assert_equal(false, Debugger.started?,
34 | 'Debugger should no longer be started.')
35 | end
36 |
37 | # Test initial variables and setting/getting state.
38 | def test_debugger_base
39 | assert_equal(false, Debugger.started?,
40 | 'Debugger should not initially be started.')
41 | Debugger.start_
42 | # we need to add the breakpoint to force enabling trace points
43 | Debugger.add_breakpoint(__FILE__, 1)
44 | assert(Debugger.started?,
45 | 'Debugger should now be started.')
46 | assert_equal(false, Debugger.debug,
47 | 'Debug variable should not be set.')
48 | assert_equal(false, Debugger.post_mortem?,
49 | 'Post mortem debugging should not be set.')
50 | a = Debugger.contexts
51 | assert_equal(1, a.size,
52 | 'There should only be one context.')
53 | assert_equal(Array, a.class,
54 | 'Context should be an array.')
55 | ensure
56 | Debugger.stop
57 | assert_equal(false, Debugger.started?,
58 | 'debugger should no longer be started.')
59 | end
60 |
61 | # Test breakpoint handling
62 | def test_breakpoints
63 | Debugger.start_
64 | assert_equal(0, Debugger.breakpoints.size,
65 | 'There should not be any breakpoints set.')
66 | brk = Debugger.add_breakpoint(__FILE__, 1)
67 | assert_equal(Debugger::Breakpoint, brk.class,
68 | 'Breakpoint should have been set and returned.')
69 | assert_equal(1, Debugger.breakpoints.size,
70 | 'There should now be one breakpoint set.')
71 | Debugger.remove_breakpoint(0)
72 | assert_equal(1, Debugger.breakpoints.size,
73 | 'There should still be one breakpoint set.')
74 | Debugger.remove_breakpoint(brk.id)
75 | assert_equal(0, Debugger.breakpoints.size,
76 | 'There should no longer be any breakpoints set.')
77 | ensure
78 | Debugger.stop
79 | end
80 | end
81 |
82 |
--------------------------------------------------------------------------------
/test/test_breakpoints.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require File.expand_path("helper", File.dirname(__FILE__))
3 |
4 | # Some tests of Debugger module in C extension ruby_debug
5 | class TestBreakpoints < Test::Unit::TestCase
6 | def test_find
7 | Debugger.start
8 | Debugger.add_breakpoint("foo.rb", 11, nil)
9 | assert_not_nil(Debugger::Breakpoint.find(Debugger.breakpoints, "foo.rb", 11, nil))
10 | assert_nil(Debugger::Breakpoint.find(Debugger.breakpoints, "bar.rb", 11, nil))
11 | assert_nil(Debugger::Breakpoint.find(Debugger.breakpoints, "foo.rb", 10, nil))
12 | end
13 |
14 | def test_conditional_true_expression
15 | Debugger.start
16 | Debugger.add_breakpoint("foo.rb", 11, "[1, 2, 3].length == 3")
17 | assert_not_nil(Debugger::Breakpoint.find(Debugger.breakpoints, "foo.rb", 11, nil))
18 | Debugger.stop
19 | end
20 |
21 | def test_conditional_false_expression
22 | Debugger.start
23 | Debugger.add_breakpoint("foo.rb", 11, "(2 + 2) == 5")
24 | assert_nil(Debugger::Breakpoint.find(Debugger.breakpoints, "foo.rb", 11, nil))
25 | Debugger.stop
26 | end
27 |
28 | def test_conditional_undefined_variable
29 | Debugger.start
30 | Debugger.add_breakpoint("foo.rb", 11, "this_variable_does_not_exist")
31 | assert_nil(Debugger::Breakpoint.find(Debugger.breakpoints, "foo.rb", 11, nil))
32 | Debugger.stop
33 | end
34 | end
--------------------------------------------------------------------------------
/test/test_catchpoint.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require File.expand_path("helper", File.dirname(__FILE__))
3 |
4 | # Test catchpoint in C ruby_debug extension.
5 |
6 | class TestRubyDebugCatchpoint < Test::Unit::TestCase
7 | def test_catchpoints
8 | assert_equal({}, Debugger.catchpoints)
9 | Debugger.start_
10 | assert_equal({}, Debugger.catchpoints)
11 | Debugger.add_catchpoint('ZeroDivisionError')
12 | assert_equal({'ZeroDivisionError' => 0}, Debugger.catchpoints)
13 | Debugger.add_catchpoint('RuntimeError')
14 | assert_equal(['RuntimeError', 'ZeroDivisionError'],
15 | Debugger.catchpoints.keys.sort)
16 | ensure
17 | Debugger.stop
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/test/test_load.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require File.expand_path("helper", File.dirname(__FILE__))
3 |
4 | # Test of Debugger.debug_load in C extension ruby_debug.so
5 | class TestDebugLoad < Test::Unit::TestCase
6 |
7 | self.test_order = :defined
8 |
9 | class << self
10 | def at_line(file, line)
11 | @@at_line = [File.basename(file), line]
12 | end
13 | end
14 |
15 | class Debugger::Context
16 | def at_line(file, line)
17 | TestDebugLoad::at_line(file, line)
18 | end
19 | end
20 |
21 | def test_debug_load
22 | src_dir = File.dirname(__FILE__)
23 | prog_script = File.join(src_dir, 'example', 'gcd.rb')
24 |
25 | # Without stopping
26 | bt = Debugger.debug_load(prog_script, false)
27 | if bt.is_a?(Exception)
28 | STDERR.puts bt.backtrace.join("\n")
29 | end
30 | assert_equal(nil, bt)
31 | assert(Debugger.started?)
32 | Debugger.stop
33 |
34 | # With stopping
35 | bt = Debugger.debug_load(prog_script, true)
36 | assert_equal(nil, bt)
37 | assert_equal(['gcd.rb', 4], @@at_line)
38 | assert(Debugger.started?)
39 | Debugger.stop
40 |
41 | # Test that we get a proper backtrace on a script that raises 'abc'
42 | prog_script = File.join(src_dir, 'example', 'raise.rb')
43 | bt = Debugger.debug_load(prog_script, false)
44 | assert_equal('abc', bt.to_s)
45 | assert(Debugger.started?)
46 | Debugger.stop
47 | ensure
48 | Debugger.stop if Debugger.started?
49 | end
50 |
51 | module MyBootsnap
52 | def load_iseq(path)
53 | iseq = RubyVM::InstructionSequence.compile_file(path)
54 |
55 | Debugger.unset_iseq_flags(iseq)
56 | iseq
57 | end
58 | end
59 |
60 | def test_bootsnap
61 | @@at_line = nil
62 | src_dir = File.dirname(__FILE__)
63 | prog_script = File.join(src_dir, 'example', 'bootsnap', 'bootsnap.rb')
64 |
65 | class << RubyVM::InstructionSequence
66 | prepend MyBootsnap
67 | end
68 | bt = Debugger.debug_load(prog_script, true)
69 | assert_equal(nil, bt)
70 | assert_not_nil(@@at_line)
71 | if RUBY_VERSION >= '2.5' && RUBY_VERSION < '2.6'
72 | assert_equal(['debase.rb', 101], @@at_line)
73 | end
74 |
75 | assert(Debugger.started?)
76 | Debugger.stop
77 |
78 | class << RubyVM::InstructionSequence; self end.class_eval { undef_method :load_iseq }
79 |
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/test/test_reload_bug.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require File.expand_path("helper", File.dirname(__FILE__))
3 |
4 | class TestReloadBug < Test::Unit::TestCase
5 | def test_reload_bug
6 | assert_equal({}, Debugger::source_reload)
7 | end
8 | end
9 |
--------------------------------------------------------------------------------