├── .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 | [![Gem Version](https://badge.fury.io/rb/debase.png)](https://rubygems.org/gems/debase) 2 | [![Build Status](https://travis-ci.org/ruby-debug/debase.svg?branch=master)](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 | --------------------------------------------------------------------------------