From 0208503e9fa298e2da9fc5f0d92db7d82e155d2c Mon Sep 17 00:00:00 2001 From: Oleg Sukhodolsky Date: Mon, 31 Aug 2015 15:45:19 +0300 Subject: [PATCH 001/104] Changelog updated for 0.6.0 --- ChangeLog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index a739807..f96699b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,4 @@ -## [master](https://github.com/ruby-debug/ruby-debug-ide/compare/v0.5.0...master) +## [0.6.0](https://github.com/ruby-debug/ruby-debug-ide/compare/v0.5.0...0.6.0) * "file-filter on|off" command added * "include file|dir" command added From 62c83c8633d2569feb969f7ebf606f92e6889377 Mon Sep 17 00:00:00 2001 From: Oleg Sukhodolsky Date: Mon, 31 Aug 2015 15:48:04 +0300 Subject: [PATCH 002/104] Let's use Debugger#remove_catchpoint and Debugger#clear_catchpoints if available These methods allow some optimisations in debugger's backend --- ChangeLog.md | 4 ++++ lib/ruby-debug-ide/commands/catchpoint.rb | 23 +++++++++++++++++++---- lib/ruby-debug-ide/version.rb | 2 +- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index f96699b..8593f56 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,7 @@ +## [master](https://github.com/ruby-debug/ruby-debug-ide/compare/v0.6.0...master) + +* let's use Debugger#remove_catchpoint and Debugger#clear_catchpoints if available + ## [0.6.0](https://github.com/ruby-debug/ruby-debug-ide/compare/v0.5.0...0.6.0) * "file-filter on|off" command added diff --git a/lib/ruby-debug-ide/commands/catchpoint.rb b/lib/ruby-debug-ide/commands/catchpoint.rb index f6e9592..eac29d3 100644 --- a/lib/ruby-debug-ide/commands/catchpoint.rb +++ b/lib/ruby-debug-ide/commands/catchpoint.rb @@ -16,21 +16,20 @@ def execute elsif not @match[2] # One arg given. if 'off' == excn - Debugger.catchpoints.clear + clear_catchpoints else Debugger.add_catchpoint(excn) print_catchpoint_set(excn) end elsif @match[2] != 'off' errmsg "Off expected. Got %s\n", @match[2] - elsif Debugger.catchpoints.member?(excn) - Debugger.catchpoints.delete(excn) + elsif remove_catchpoint(excn) print_catchpoint_deleted(excn) else errmsg "Catch for exception %s not found.\n", excn end end - + class << self def help_command 'catch' @@ -45,5 +44,21 @@ def help(cmd) } end end + + private + + def clear_catchpoints + if Debugger.respond_to?(:clear_catchpoints) + Debugger.clear_catchpoints + else + Debugger.catchpoints.clear + end + end + + def remove_catchpoint(excn) + return Debugger.remove_catchpoint(excn) if Debugger.respond_to?(:remove_catchpoint) + return Debugger.catchpoints.delete(excn) if Debugger.catchpoints.member?(excn) + false + end end end diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index b8e0c25..1b8ca16 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.0' + IDE_VERSION='0.6.1.beta1' end From 17b9d602923fdce28f8fe600bddf56ba4760425c Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Thu, 8 Oct 2015 19:48:11 +0300 Subject: [PATCH 003/104] More path escaping to prevent faults on paths with ampersand --- lib/ruby-debug-ide/xml_printer.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 3ef5140..1e729d6 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -199,13 +199,13 @@ def print_file_filter_status(status) def print_breakpoints(breakpoints) print_element 'breakpoints' do breakpoints.sort_by{|b| b.id }.each do |b| - print "", b.id, b.source, b.pos.to_s + print "", b.id, CGI.escapeHTML(b.source), b.pos.to_s end end end def print_breakpoint_added(b) - print "", b.id, b.source, b.pos + print "", b.id, CGI.escapeHTML(b.source), b.pos end def print_breakpoint_deleted(b) @@ -289,14 +289,14 @@ def print_methods(methods) # Events def print_breakpoint(_, breakpoint) - print("", - breakpoint.source, breakpoint.pos, Debugger.current_context.thnum) + print("", + CGI.escapeHTML(breakpoint.source), breakpoint.pos, Debugger.current_context.thnum) end def print_catchpoint(exception) context = Debugger.current_context print("", - context.frame_file(0), context.frame_line(0), exception.class, CGI.escapeHTML(exception.to_s), context.thnum) + CGI.escapeHTML(context.frame_file(0)), context.frame_line(0), exception.class, CGI.escapeHTML(exception.to_s), context.thnum) end def print_trace(context, file, line) From ca281ed7bcf0fbfd1af39077fef46fb3c6e7f520 Mon Sep 17 00:00:00 2001 From: Robert Reiz Date: Thu, 5 Nov 2015 15:48:21 +0100 Subject: [PATCH 004/104] Adding MIT license to the gemspec. --- ruby-debug-ide.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/ruby-debug-ide.gemspec b/ruby-debug-ide.gemspec index 00d2e45..b448205 100644 --- a/ruby-debug-ide.gemspec +++ b/ruby-debug-ide.gemspec @@ -32,6 +32,7 @@ EOF spec.author = "Markus Barchfeld, Martin Krauskopf, Mark Moseley, JetBrains RubyMine Team" spec.email = "rubymine-feedback@jetbrains.com" + spec.license = "MIT" spec.platform = Gem::Platform::RUBY spec.require_path = "lib" spec.bindir = "bin" From 9fe6aa34012092372c413797e7082c41bde97a60 Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Mon, 14 Mar 2016 20:01:36 +0300 Subject: [PATCH 005/104] don't break if object doesn't support to_s (e.g. BasicObject) --- lib/ruby-debug-ide/xml_printer.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 1e729d6..00f08b5 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -401,7 +401,11 @@ def build_compact_value_attr(value, value_str) end def safe_to_string(value) - str = value.to_s + begin + str = value.to_s + rescue NoMethodError + str = "(Object doesn't support #to_s)" + end return str unless str.nil? string_io = StringIO.new From ce3dbe0a76e11f34abf60740b7c88b23a1c3b5bf Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Mon, 21 Mar 2016 14:04:00 +0300 Subject: [PATCH 006/104] bump --- lib/ruby-debug-ide/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index 1b8ca16..48c578c 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1.beta1' + IDE_VERSION='0.6.1.beta2' end From 3681a0592cb8894f8c89b99c7a11bc4ca0729374 Mon Sep 17 00:00:00 2001 From: "dmitrii.kravchenko" Date: Mon, 8 Aug 2016 16:30:48 +0300 Subject: [PATCH 007/104] attach mode added --- bin/rdebug-ide | 28 ++++++++++++++------ lib/ruby-debug-ide.rb | 13 ++++----- lib/ruby-debug-ide/commands/control.rb | 27 +++++++++++++++++++ lib/ruby-debug-ide/ide_processor.rb | 5 ++-- lib/ruby-debug-ide/multiprocess/pre_child.rb | 8 +++--- 5 files changed, 59 insertions(+), 22 deletions(-) diff --git a/bin/rdebug-ide b/bin/rdebug-ide index 574dc44..d73b94d 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -22,7 +22,8 @@ options = OpenStruct.new( 'evaluation_timeout' => 10, 'rm_protocol_extensions' => false, 'catchpoint_deleted_event' => false, - 'value_as_nested_element' => false + 'value_as_nested_element' => false, + 'attach_mode' => false ) opts = OptionParser.new do |opts| @@ -54,7 +55,9 @@ EOB opts.on("-I", "--include PATH", String, "Add PATH to $LOAD_PATH") do |path| $LOAD_PATH.unshift(path) end - + opts.on("--attach-mode", "Tells that rdebug-ide is working in attach mode") do + options.attach_mode = true + end opts.on("--keep-frame-binding", "Keep frame bindings") {options.frame_bind = true} opts.on("--disable-int-handler", "Disables interrupt signal handler") {options.int_handler = false} opts.on("--rubymine-protocol-extensions", "Enable all RubyMine-specific incompatible protocol extensions") do @@ -89,15 +92,17 @@ rescue StandardError => e exit(1) end -if ARGV.empty? +if ARGV.empty? && !options.attach_mode puts opts puts puts "Must specify a script to run" exit(1) -end +end -# save script name -Debugger::PROG_SCRIPT = ARGV.shift +unless options.attach_mode + # save script name + Debugger::PROG_SCRIPT = ARGV.shift +end if options.dispatcher_port != -1 ENV['IDE_PROCESS_DISPATCHER'] = options.dispatcher_port.to_s @@ -119,7 +124,7 @@ if options.int_handler # install interruption handler trap('INT') { Debugger.interrupt_last } end - + # set options Debugger.keep_frame_binding = options.frame_bind Debugger.tracing = options.tracing @@ -127,5 +132,12 @@ Debugger.evaluation_timeout = options.evaluation_timeout Debugger.catchpoint_deleted_event = options.catchpoint_deleted_event || options.rm_protocol_extensions Debugger.value_as_nested_element = options.value_as_nested_element || options.rm_protocol_extensions -Debugger.debug_program(options) +if options.attach_mode + Debugger::MultiProcess::pre_child(options) + if Debugger::FRONT_END == "debase" + Debugger.enable_trace_points + end +else + Debugger.debug_program(options) +end diff --git a/lib/ruby-debug-ide.rb b/lib/ruby-debug-ide.rb index 9704f94..4e0552b 100644 --- a/lib/ruby-debug-ide.rb +++ b/lib/ruby-debug-ide.rb @@ -4,14 +4,16 @@ require 'thread' if RUBY_VERSION < '2.0' || defined?(JRUBY_VERSION) require 'ruby-debug-base' + Debugger::FRONT_END = "ruby-debug-base" else require 'debase' + Debugger::FRONT_END = "debase" end -require 'ruby-debug-ide/version' -require 'ruby-debug-ide/xml_printer' -require 'ruby-debug-ide/ide_processor' -require 'ruby-debug-ide/event_processor' +require_relative 'ruby-debug-ide/version' +require_relative 'ruby-debug-ide/xml_printer' +require_relative 'ruby-debug-ide/ide_processor' +require_relative 'ruby-debug-ide/event_processor' module Debugger @@ -110,7 +112,6 @@ def start_control(host, port, notify_dispatcher) server = TCPServer.new(host, port) print_greeting_msg(host, port) notify_dispatcher(port) if notify_dispatcher - while (session = server.accept) $stderr.puts "Connected from #{session.peeraddr[2]}" if Debugger.cli_debug dispatcher = ENV['IDE_PROCESS_DISPATCHER'] @@ -160,8 +161,8 @@ def notify_dispatcher(port) return unless ENV['IDE_PROCESS_DISPATCHER'] acceptor_host, acceptor_port = ENV['IDE_PROCESS_DISPATCHER'].split(":") acceptor_host, acceptor_port = '127.0.0.1', acceptor_host unless acceptor_port - connected = false + 3.times do |i| begin s = TCPSocket.open(acceptor_host, acceptor_port) diff --git a/lib/ruby-debug-ide/commands/control.rb b/lib/ruby-debug-ide/commands/control.rb index c8ff56f..0fffb4d8 100644 --- a/lib/ruby-debug-ide/commands/control.rb +++ b/lib/ruby-debug-ide/commands/control.rb @@ -126,4 +126,31 @@ def help(cmd) end end end + + + class DetachCommand < Command # :nodoc: + self.control = true + + def regexp + /^\s*detach\s*$/ + end + + def execute + Debugger.stop + Debugger.control_thread = nil + Thread.current.exit #@control_thread is a current thread + end + + class << self + def help_command + 'detach' + end + + def help(cmd) + %{ + detach\ndetach debugger\nnote: this option is only for remote debugging (or local attach) + } + end + end + end end diff --git a/lib/ruby-debug-ide/ide_processor.rb b/lib/ruby-debug-ide/ide_processor.rb index e5b7e98..ac07a0f 100644 --- a/lib/ruby-debug-ide/ide_processor.rb +++ b/lib/ruby-debug-ide/ide_processor.rb @@ -1,5 +1,5 @@ -require 'ruby-debug-ide/interface' -require 'ruby-debug-ide/command' +require_relative 'interface' +require_relative 'command' module Debugger class IdeCommandProcessor @@ -77,7 +77,6 @@ def process_commands ctrl_cmd_classes = Command.commands.select{|cmd| cmd.control} state = ControlState.new(@interface) ctrl_cmds = ctrl_cmd_classes.map{|cmd| cmd.new(state, @printer)} - while input = @interface.read_command # escape % since print_debug might use printf # sleep 0.3 diff --git a/lib/ruby-debug-ide/multiprocess/pre_child.rb b/lib/ruby-debug-ide/multiprocess/pre_child.rb index 1b9c2cc..eb96242 100644 --- a/lib/ruby-debug-ide/multiprocess/pre_child.rb +++ b/lib/ruby-debug-ide/multiprocess/pre_child.rb @@ -1,19 +1,17 @@ module Debugger module MultiProcess class << self - def pre_child - + def pre_child(options = nil) require 'socket' require 'ostruct' host = ENV['DEBUGGER_HOST'] - port = find_free_port(host) - options = OpenStruct.new( + options ||= OpenStruct.new( 'frame_bind' => false, 'host' => host, 'load_mode' => false, - 'port' => port, + 'port' => find_free_port(host), 'stop' => false, 'tracing' => false, 'int_handler' => true, From 72b1c4af766f3926c9373a257efcb58bb65d27da Mon Sep 17 00:00:00 2001 From: "dmitrii.kravchenko" Date: Mon, 8 Aug 2016 16:59:19 +0300 Subject: [PATCH 008/104] gdb_wrapper and debugger_loader added --- bin/gdb_wrapper | 93 ++++++++++++++++++++ lib/ruby-debug-ide/attach/debugger_loader.rb | 20 +++++ 2 files changed, 113 insertions(+) create mode 100755 bin/gdb_wrapper create mode 100644 lib/ruby-debug-ide/attach/debugger_loader.rb diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper new file mode 100755 index 0000000..c56597d --- /dev/null +++ b/bin/gdb_wrapper @@ -0,0 +1,93 @@ +#!/usr/bin/env ruby + +require 'optparse' +require 'ostruct' + +options = OpenStruct.new( + 'pid' => nil, + 'sdk_path' => nil, + 'uid' => nil, + 'gems_to_include' => [] +) + +opts = OptionParser.new do |opts| + # TODO need some banner + opts.banner = < Date: Tue, 9 Aug 2016 14:16:01 +0300 Subject: [PATCH 009/104] closing interface --- lib/ruby-debug-ide/commands/control.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ruby-debug-ide/commands/control.rb b/lib/ruby-debug-ide/commands/control.rb index 0fffb4d8..f518c02 100644 --- a/lib/ruby-debug-ide/commands/control.rb +++ b/lib/ruby-debug-ide/commands/control.rb @@ -137,6 +137,7 @@ def regexp def execute Debugger.stop + Debugger.interface.close Debugger.control_thread = nil Thread.current.exit #@control_thread is a current thread end From fa128e1b496cfdd74f0cff78f773f5a3228dc56e Mon Sep 17 00:00:00 2001 From: "dmitrii.kravchenko" Date: Mon, 15 Aug 2016 16:00:15 +0300 Subject: [PATCH 010/104] relative imports replaced with normal ones. `print_greeting_msg` now in separate file. `enable_trace_points` call (wrong) replaced with empty_file loading (ugly but should always work). Including gems into $LOAD_PATH. --- bin/gdb_wrapper | 39 +++++++++++-------- bin/rdebug-ide | 11 ++++-- lib/ruby-debug-ide.rb | 25 ++---------- lib/ruby-debug-ide/attach/debugger_loader.rb | 6 +-- lib/ruby-debug-ide/attach/empty_file.rb | 0 lib/ruby-debug-ide/greeter.rb | 40 ++++++++++++++++++++ lib/ruby-debug-ide/ide_processor.rb | 4 +- ruby-debug-ide.gemspec | 2 +- 8 files changed, 81 insertions(+), 46 deletions(-) create mode 100644 lib/ruby-debug-ide/attach/empty_file.rb create mode 100644 lib/ruby-debug-ide/greeter.rb diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index c56597d..ba56a55 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -13,15 +13,15 @@ options = OpenStruct.new( opts = OptionParser.new do |opts| # TODO need some banner opts.banner = < Date: Wed, 31 Aug 2016 19:14:11 +0300 Subject: [PATCH 011/104] bug fix: cli_debug should be part of options because pre_child sets Debugger.cli_debug = options.cli_debug; bug fix: check RUBYOPT and $: if they already contain our paths (relevant in case of attaching several time to some process); bug fix: calling `prepare_context` instead of `enable_tracepoints`; calling `init_variables` to reset all debase variables from previous attaching --- bin/rdebug-ide | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/bin/rdebug-ide b/bin/rdebug-ide index 6ba9047..26295f3 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -23,7 +23,8 @@ options = OpenStruct.new( 'rm_protocol_extensions' => false, 'catchpoint_deleted_event' => false, 'value_as_nested_element' => false, - 'attach_mode' => false + 'attach_mode' => false, + 'cli_debug' => false ) opts = OptionParser.new do |opts| @@ -48,6 +49,7 @@ EOB opts.on("-l", "--load-mode", "load mode (experimental)") {options.load_mode = true} opts.on("-d", "--debug", "Debug self - prints information for debugging ruby-debug itself") do Debugger.cli_debug = true + options.cli_debug = true end opts.on("--xml-debug", "Debug self - sends information s for debugging ruby-debug itself") do Debugger.xml_debug = true @@ -109,16 +111,20 @@ end if options.dispatcher_port != -1 ENV['IDE_PROCESS_DISPATCHER'] = options.dispatcher_port.to_s if RUBY_VERSION < "1.9" - $: << File.expand_path(File.dirname(__FILE__) + "/../lib/") + lib_path = File.expand_path(File.dirname(__FILE__) + "/../lib/") + $: << lib_path unless $:.include? lib_path require 'ruby-debug-ide/multiprocess' else require_relative '../lib/ruby-debug-ide/multiprocess' end ENV['DEBUGGER_STORED_RUBYLIB'] = ENV['RUBYLIB'] - old_opts = ENV['RUBYOPT'] - ENV['RUBYOPT'] = "-r#{File.expand_path(File.dirname(__FILE__))}/../lib/ruby-debug-ide/multiprocess/starter" - ENV['RUBYOPT'] += " #{old_opts}" if old_opts + old_opts = ENV['RUBYOPT'] || '' + starter = "-r#{File.expand_path(File.dirname(__FILE__))}/../lib/ruby-debug-ide/multiprocess/starter" + unless old_opts.include? starter + ENV['RUBYOPT'] = starter + ENV['RUBYOPT'] += " #{old_opts}" if old_opts != '' + end ENV['DEBUGGER_CLI_DEBUG'] = Debugger.cli_debug.to_s end @@ -135,13 +141,15 @@ Debugger.catchpoint_deleted_event = options.catchpoint_deleted_event || options. Debugger.value_as_nested_element = options.value_as_nested_element || options.rm_protocol_extensions if options.attach_mode + if Debugger::FRONT_END == "debase" + Debugger.init_variables + end + Debugger::MultiProcess::pre_child(options) - # This will trigger `setup_tracepoints` and `prepare_context` (which is private in debase) - # without any actual excessive code execution. if Debugger::FRONT_END == "debase" - EMPTY_TEMPLATE = File.expand_path(File.dirname(__FILE__)) + '/../lib/ruby-debug-ide/attach/empty_file.rb' - Debugger.debug_load(EMPTY_TEMPLATE) + Debugger.setup_tracepoints + Debugger.prepare_context end else Debugger.debug_program(options) From 0c2a3295c91306ca57acc7a39dfc2fefab06ffca Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Wed, 31 Aug 2016 21:45:46 +0300 Subject: [PATCH 012/104] added interaction with gdb, added c-level attaching --- bin/gdb_wrapper | 253 +++++++++++++++++++----- ext/Makefile | 10 + ext/do_attach.c | 37 ++++ ext/do_attach.h | 10 + lib/ruby-debug-ide/attach/empty_file.rb | 0 5 files changed, 266 insertions(+), 44 deletions(-) create mode 100644 ext/Makefile create mode 100644 ext/do_attach.c create mode 100644 ext/do_attach.h delete mode 100644 lib/ruby-debug-ide/attach/empty_file.rb diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index ba56a55..4562384 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -3,11 +3,14 @@ require 'optparse' require 'ostruct' +$stdout.sync = true +$stderr.sync = true + options = OpenStruct.new( - 'pid' => nil, - 'sdk_path' => nil, - 'uid' => nil, - 'gems_to_include' => [] + 'pid' => nil, + 'sdk_path' => nil, + 'uid' => nil, + 'gems_to_include' => [] ) opts = OptionParser.new do |opts| @@ -16,19 +19,19 @@ opts = OptionParser.new do |opts| Some useful banner. EOB - opts.on("--pid PID", "pid of process you want to attach to for debugging") do |pid| + opts.on('--pid PID', 'pid of process you want to attach to for debugging') do |pid| options.pid = pid end - opts.on("--ruby-path SDK_PATH", "path to ruby interpreter") do |ruby_path| + opts.on('--ruby-path RUBY_PATH', 'path to ruby interpreter') do |ruby_path| options.ruby_path = ruby_path end - opts.on("--uid UID", "uid which this process should set after executing gdb attach") do |uid| + opts.on('--uid UID', 'uid which this process should set after executing gdb attach') do |uid| options.uid = uid end - opts.on("--include-gem GEM_LIB_PATH", "lib of gem to include") do |gem_lib_path| + opts.on('--include-gem GEM_LIB_PATH', 'lib of gem to include') do |gem_lib_path| options.gems_to_include << gem_lib_path end end @@ -36,62 +39,224 @@ end opts.parse! ARGV unless options.pid - $stderr.puts "You must specify PID of process you want to attach to" + $stderr.puts 'You should specify PID of process you want to attach to' exit 1 end unless options.ruby_path - $stderr.puts "You must specify RUBY_PATH of ruby interpreter" + $stderr.puts 'You should specify path to the ruby interpreter' exit 1 end -# TODO Denis told not to implement this hack -# So this is only for me while debugging as -# I don't want to get any warnings. -sigints_caught = 0 -trap('INT') do - sigints_caught += 1 - if sigints_caught == 2 - exit 0 - end -end - argv = '["' + ARGV * '", "' + '"]' gems_to_include = '["' + options.gems_to_include * '", "' + '"]' -commands_list = [] +path_to_debugger_loader = File.expand_path(File.dirname(__FILE__)) + '/../lib/ruby-debug-ide/attach/debugger_loader' -def commands_list.<<(command) - self.push "-ex \"#{command}\"" +options.gems_to_include.each do |gem_path| + $LOAD_PATH.unshift(gem_path) unless $LOAD_PATH.include?(gem_path) end -path_to_debugger_loader = File.expand_path(File.dirname(__FILE__)) + '/../lib/ruby-debug-ide/attach/debugger_loader' +require 'ruby-debug-ide/greeter' +Debugger::print_greeting_msg(nil, nil) -# rb_finish: wait while execution comes to the next line. -# This is essential because we could interrupt process in a middle -# of some evaluations (e.g., system call) -commands_list << "call rb_eval_string_protect(\\\"set_trace_func lambda{|event, file, line, id, binding, classname| if /line/ =~ event; sleep 0; set_trace_func(nil); end}\\\", (int *)0)" -commands_list << "tbreak rb_f_sleep" -commands_list << "cont" +$pid = options.pid +$last_bt = '' +$gdb_tmp_file = '/tmp/gdb_out.txt' -# evalr: loading debugger into the process -evalr = "call rb_eval_string_protect(%s, (int *)0)" -commands_list << ("#{evalr}" % ["(\\\"require '#{path_to_debugger_loader}'; load_debugger(#{gems_to_include.gsub("\"", "'")}, #{argv.gsub("\"", "'")})\\\")"]) +begin + file = File.open($gdb_tmp_file, 'w') + file.truncate(0) + file.close +rescue Exception => e + $stderr.puts e + $stderr.puts "Could not create file #{$gdb_tmp_file} for gdb logging. Aborting." + exit! +end -# q: exit gdb and continue process execution with debugger -commands_list << "q" +gdb_executed_all_commands = false -cmd = "gdb #{options.ruby_path} #{options.pid} -nh -nx -batch #{commands_list.join(" ")}" +IO.popen("gdb #{options.ruby_path} #{options.pid} -nh -nx", 'r+') do |gdb| -options.gems_to_include.each do |gem_path| - $LOAD_PATH.unshift(gem_path) unless $LOAD_PATH.include?(gem_path) -end + $gdb = gdb + $main_thread = nil -require 'ruby-debug-ide/greeter' -Debugger::print_greeting_msg(nil, nil) -$stderr.puts "Running command #{cmd}" + class ProcessThread + + attr_reader :thread_num, :is_main + + def initialize(thread_num, is_main) + @thread_num = thread_num + @is_main = is_main + end + + def switch + $gdb.execute "thread #{thread_num}" + end + + def finish + $gdb.finish + end + + def get_bt + return $gdb.execute 'bt' + end + + def top_caller_match(bt, pattern) + return bt.split('#')[1] =~ /#{pattern}/ + end + + def any_caller_match(bt, pattern) + return bt =~ /#{pattern}/ + end + + def is_inside_malloc(bt = get_bt) + if any_caller_match(bt, '(malloc\.c)') + $stderr.puts "process #{$pid} is currently inside malloc." + return true + else + return false + end + end + + def is_inside_gc(bt = get_bt) + if any_caller_match(bt, '(gc\.c)') + $stderr.puts "process #{$pid} is currently in garbage collection phase." + return true + else + return false + end + end + + def need_finish_frame + bt = get_bt + return is_inside_malloc(bt) || is_inside_gc(bt) + end + + end + + def gdb.update_threads + process_threads = [] + info_threads = (self.execute 'info threads').split("\n") + # first line of gdb's response is ` Id Target Id Frame` info line + # last line of gdb's response is `(gdb) ` + info_threads.shift + info_threads.pop + # each thread info looks like this: + # 3 Thread 0x7ff535405700 (LWP 8291) "ruby-timer-thr" 0x00007ff534a15fdd in poll () at ../sysdeps/unix/syscall-template.S:81 + info_threads.each do |thread_info| + next unless thread_info =~ /[\s*]*\d+\s+Thread.*/ + $stderr.puts "thread_info: #{thread_info}" + is_main = thread_info[0] == '*' + thread_info.sub!(/[\s*]*/, '') + thread_info.sub!(/\s.*$/, '') + thread = ProcessThread.new(thread_info.to_i, is_main) + if thread.is_main + $main_thread = thread + end + process_threads << thread + end + process_threads + end + + def gdb.get_response + content = '' + loop do + sleep 0.01 # give time to gdb to finish command execution and print it to file + file = File.open($gdb_tmp_file, 'r') + content = file.read + file.close + break if content =~ /\(gdb\)\s\z/ + end + content + end + + def gdb.enable_logging + self.puts 'set logging on' + end + + def gdb.disable_logging + self.puts 'set logging off' + end + + def gdb.overwrite_file + disable_logging + enable_logging + end + + def gdb.execute(command) + self.overwrite_file + self.puts command + $stdout.puts "executed command '#{command}' inside gdb." + if command == 'q' + return '' + end + response = self.get_response + if command == 'bt' + $last_bt = response + end + return response + end + + def gdb.finish + $stdout.puts 'trying to finish current frame.' + self.execute 'finish' + end + + def gdb.set_logging + self.puts "set logging file #{$gdb_tmp_file}" + self.puts 'set logging overwrite on' + self.puts 'set logging redirect on' + self.enable_logging + + $stdout.puts "all gdb output redirected to #{$gdb_tmp_file}." + end + + def gdb.check_already_under_debug + threads = self.execute 'info threads' + return threads =~ /ruby-debug-ide/ + end -`#{cmd}` or raise "GDB failed. Aborting." + gdb.set_logging + + if gdb.check_already_under_debug + $stderr.puts "Process #{$pid} is already under debug" + gdb.execute 'q' + end + + gdb.execute 'set scheduler-locking off' + gdb.execute 'set unwindonsignal on' + + should_check_threads_state = true + + while should_check_threads_state + should_check_threads_state = false + gdb.update_threads.each do |thread| + thread.switch + while thread.need_finish_frame + should_check_threads_state = true + thread.finish + end + end + end + + $main_thread.switch + + gdb.execute "call dlopen(\"/home/equi/Job/JetBrains/Internship_2016/ruby-debug-ide/ext/libAttach.so\", 2)" + gdb.execute "call start_attach(\"require '#{path_to_debugger_loader}'; load_debugger(#{gems_to_include.gsub("\"", "'")}, #{argv.gsub("\"", "'")})\")" + + gdb_executed_all_commands = true + gdb.execute 'q' + +end + +trap('INT') do + unless gdb_executed_all_commands + $stderr.puts "Seems like could not attach to process. Its backtrace:\n#{$last_bt}" + $stderr.flush + end + exit 1 +end if options.uid Process::Sys.setuid(options.uid.to_i) diff --git a/ext/Makefile b/ext/Makefile new file mode 100644 index 0000000..9bec56c --- /dev/null +++ b/ext/Makefile @@ -0,0 +1,10 @@ +all: libAttach.so + +libAttach.so: libAttach.o + gcc -shared -o libAttach.so libAttach.o + +libAttach.o: do_attach.c + gcc -Wall -g -fPIC -c -I/home/equi/.rvm/rubies/ruby-2.3.1/include/ruby-2.3.0 -I/home/equi/.rvm/rubies/ruby-2.3.1/include/ruby-2.3.0/x86_64-linux/ do_attach.c -o libAttach.o + +clean: + rm libAttach.* diff --git a/ext/do_attach.c b/ext/do_attach.c new file mode 100644 index 0000000..7291b48 --- /dev/null +++ b/ext/do_attach.c @@ -0,0 +1,37 @@ +#include "do_attach.h" + +static const char *_command_to_eval; + +static int +__check_gc(void) +{ + if (rb_during_gc()) { + fprintf(stderr, "Can not connect during garbage collection phase. Please, try again later.\n"); + return 1; + } + return 0; +} + +static void +__catch_line_event(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE klass) +{ + (void)sizeof(evflag); + (void)sizeof(self); + (void)sizeof(mid); + (void)sizeof(klass); + + rb_remove_event_hook(__catch_line_event); + if (__check_gc()) + return; + rb_eval_string_protect(_command_to_eval, NULL); // TODO pass something more useful than NULL +} + +void +start_attach(const char* command) +{ + _command_to_eval = command; + if (__check_gc()) + return; + rb_global_variable((VALUE *) _command_to_eval); + rb_add_event_hook(__catch_line_event, RUBY_EVENT_LINE, (VALUE) NULL); +} diff --git a/ext/do_attach.h b/ext/do_attach.h new file mode 100644 index 0000000..3697d3f --- /dev/null +++ b/ext/do_attach.h @@ -0,0 +1,10 @@ +#ifndef __DO_ATTACH_H__ +#define __DO_ATTACH_H__ + +#include +#include +#include + +void start_attach(const char *command); + +#endif //__DO_ATTACH_H__ \ No newline at end of file diff --git a/lib/ruby-debug-ide/attach/empty_file.rb b/lib/ruby-debug-ide/attach/empty_file.rb deleted file mode 100644 index e69de29..0000000 From 9c96a7689f8cc5249e3fad5cdd71470a550f914a Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Thu, 1 Sep 2016 14:20:56 +0300 Subject: [PATCH 013/104] added `attached` flag to indicate if we are currently under debug. This is needed for attach mode: if we detached `pre_child` should not be called in `fork` and `exec` --- bin/rdebug-ide | 2 ++ lib/ruby-debug-ide.rb | 1 + lib/ruby-debug-ide/commands/control.rb | 1 + lib/ruby-debug-ide/multiprocess/pre_child.rb | 2 ++ 4 files changed, 6 insertions(+) diff --git a/bin/rdebug-ide b/bin/rdebug-ide index 26295f3..dced3a7 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -140,6 +140,8 @@ Debugger.evaluation_timeout = options.evaluation_timeout Debugger.catchpoint_deleted_event = options.catchpoint_deleted_event || options.rm_protocol_extensions Debugger.value_as_nested_element = options.value_as_nested_element || options.rm_protocol_extensions +Debugger.attached = true + if options.attach_mode if Debugger::FRONT_END == "debase" Debugger.init_variables diff --git a/lib/ruby-debug-ide.rb b/lib/ruby-debug-ide.rb index db12c79..30fc079 100644 --- a/lib/ruby-debug-ide.rb +++ b/lib/ruby-debug-ide.rb @@ -43,6 +43,7 @@ def cleanup_backtrace(backtrace) cleared end + attr_accessor :attached attr_accessor :cli_debug, :xml_debug, :evaluation_timeout attr_accessor :control_thread attr_reader :interface diff --git a/lib/ruby-debug-ide/commands/control.rb b/lib/ruby-debug-ide/commands/control.rb index f518c02..bc09bda 100644 --- a/lib/ruby-debug-ide/commands/control.rb +++ b/lib/ruby-debug-ide/commands/control.rb @@ -136,6 +136,7 @@ def regexp end def execute + Debugger.attached = false Debugger.stop Debugger.interface.close Debugger.control_thread = nil diff --git a/lib/ruby-debug-ide/multiprocess/pre_child.rb b/lib/ruby-debug-ide/multiprocess/pre_child.rb index eb96242..7a2f4fc 100644 --- a/lib/ruby-debug-ide/multiprocess/pre_child.rb +++ b/lib/ruby-debug-ide/multiprocess/pre_child.rb @@ -2,6 +2,8 @@ module Debugger module MultiProcess class << self def pre_child(options = nil) + return unless Debugger.attached + require 'socket' require 'ostruct' From 32f61feb231bf3953027bf2a0bd79add4b41af73 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Fri, 2 Sep 2016 21:19:26 +0300 Subject: [PATCH 014/104] lldb added --- bin/gdb_wrapper | 378 +++++++++++++++++++++++++++++------------------- ext/do_attach.c | 14 +- ext/do_attach.h | 2 +- 3 files changed, 244 insertions(+), 150 deletions(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index 4562384..5482744 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -6,7 +6,7 @@ require 'ostruct' $stdout.sync = true $stderr.sync = true -options = OpenStruct.new( +@options = OpenStruct.new( 'pid' => nil, 'sdk_path' => nil, 'uid' => nil, @@ -20,246 +20,332 @@ Some useful banner. EOB opts.on('--pid PID', 'pid of process you want to attach to for debugging') do |pid| - options.pid = pid + @options.pid = pid end opts.on('--ruby-path RUBY_PATH', 'path to ruby interpreter') do |ruby_path| - options.ruby_path = ruby_path + @options.ruby_path = ruby_path end opts.on('--uid UID', 'uid which this process should set after executing gdb attach') do |uid| - options.uid = uid + @options.uid = uid end opts.on('--include-gem GEM_LIB_PATH', 'lib of gem to include') do |gem_lib_path| - options.gems_to_include << gem_lib_path + @options.gems_to_include << gem_lib_path end end opts.parse! ARGV -unless options.pid +unless @options.pid $stderr.puts 'You should specify PID of process you want to attach to' exit 1 end -unless options.ruby_path +unless @options.ruby_path $stderr.puts 'You should specify path to the ruby interpreter' exit 1 end -argv = '["' + ARGV * '", "' + '"]' -gems_to_include = '["' + options.gems_to_include * '", "' + '"]' +$argv = '["' + ARGV * '", "' + '"]' +$gems_to_include = '["' + @options.gems_to_include * '", "' + '"]' +$path_to_debugger_loader = File.expand_path(File.dirname(__FILE__)) + '/../lib/ruby-debug-ide/attach/debugger_loader' -path_to_debugger_loader = File.expand_path(File.dirname(__FILE__)) + '/../lib/ruby-debug-ide/attach/debugger_loader' - -options.gems_to_include.each do |gem_path| +@options.gems_to_include.each do |gem_path| $LOAD_PATH.unshift(gem_path) unless $LOAD_PATH.include?(gem_path) end require 'ruby-debug-ide/greeter' Debugger::print_greeting_msg(nil, nil) -$pid = options.pid -$last_bt = '' -$gdb_tmp_file = '/tmp/gdb_out.txt' - -begin - file = File.open($gdb_tmp_file, 'w') - file.truncate(0) - file.close -rescue Exception => e - $stderr.puts e - $stderr.puts "Could not create file #{$gdb_tmp_file} for gdb logging. Aborting." - exit! -end - -gdb_executed_all_commands = false +# this class is some kind of "interface" +class NativeDebugger -IO.popen("gdb #{options.ruby_path} #{options.pid} -nh -nx", 'r+') do |gdb| + attr_reader :pid, :last_bt - $gdb = gdb - $main_thread = nil + # @param executable -- path to ruby interpreter + # @param pid -- pid of process you want to debug + # @param options -- flags you want to specify to your debugger as a string (e.g. "-nx -nh" for gdb to disable .gdbinit) + def initialize(executable, pid, options) + @pid = pid + @main_thread = nil + @delimiter = '__OUTPUT_FINISHED__' # for getting response + @last_bt = '' # to shaw it to user - class ProcessThread + launch_string = "#{self} #{executable} #{options}" + @pipe = IO.popen(launch_string, 'r+') + $stdout.puts "executed '#{launch_string}'" + end - attr_reader :thread_num, :is_main + def attach_to_process + execute "attach #{@pid}" + end - def initialize(thread_num, is_main) - @thread_num = thread_num - @is_main = is_main + def execute(command) + @pipe.puts command + $stdout.puts "executed `#{command}` command inside #{self}." + if command == 'q' + @pipe.close + return '' end - - def switch - $gdb.execute "thread #{thread_num}" + response = get_response + if command == 'bt' + @last_bt = response end + $stdout.puts "response for #{command}:\n#{response}\n\n\n\n" + response + end - def finish - $gdb.finish - end + def get_response + # we need this hack to understand that debugger gave us all output from last executed command + @pipe.puts "print \"#{@delimiter}\"" - def get_bt - return $gdb.execute 'bt' + content = '' + loop do + line = @pipe.readline + $stderr.puts line + next if line =~ /\(lldb\)/ # lldb repeats your input to its output + break if line =~ /\$\d+\s=\s"__OUTPUT_FINISHED__"/ + content += line end + content + end - def top_caller_match(bt, pattern) - return bt.split('#')[1] =~ /#{pattern}/ - end + def update_threads - def any_caller_match(bt, pattern) - return bt =~ /#{pattern}/ - end + end - def is_inside_malloc(bt = get_bt) - if any_caller_match(bt, '(malloc\.c)') - $stderr.puts "process #{$pid} is currently inside malloc." - return true - else - return false - end + def check_already_under_debug + + end + + def switch_to_thread + + end + + def set_tbreak(str) + execute "tbreak #{str}" + end + + def continue + @pipe.puts 'c' + loop do + line = @pipe.readline + break if line =~ /__func_to_set_breakpoint_at/ end + get_response + end + + def call_start_attach + raise 'No main thread found. Did you forget to call `update_threads`?' if @main_thread == nil + @main_thread.switch + end + + def exit + execute 'q' + end + + def to_s + 'native_debugger' + end - def is_inside_gc(bt = get_bt) - if any_caller_match(bt, '(gc\.c)') - $stderr.puts "process #{$pid} is currently in garbage collection phase." - return true - else - return false +end + +class LLDB < NativeDebugger + + def initialize(executable, pid, options) + super(executable, pid, options) + end + + def set_flags + + end + + def update_threads + process_threads = [] + info_threads = (execute 'thread list').split("\n") + info_threads.each do |thread_info| + next unless thread_info =~ /[\s*]*thread\s#\d+.*/ + $stdout.puts "thread_info: #{thread_info}" + is_main = thread_info[0] == '*' + thread_num = thread_info.sub(/[\s*]*thread\s#/, '').sub(/:\s.*$/, '').to_i + thread = ProcessThread.new(thread_num, is_main, self) + if thread.is_main + @main_thread = thread end + process_threads << thread end + process_threads + end - def need_finish_frame - bt = get_bt - return is_inside_malloc(bt) || is_inside_gc(bt) - end + def check_already_under_debug + threads = execute 'thread list' + threads =~ /ruby-debug-ide/ + end + + def switch_to_thread(thread_num) + execute "thread select #{thread_num}" + end + + def call_start_attach + super() + execute "expr (void *) dlopen(\"/home/equi/Job/JetBrains/Internship_2016/ruby-debug-ide/ext/libAttach.so\", 2)" + execute "call start_attach(\"require '#{$path_to_debugger_loader}'; load_debugger(#{$gems_to_include.gsub("\"", "'")}, #{$argv.gsub("\"", "'")})\")" + end + + def to_s + 'lldb-3.8' + end +end + +class GDB < NativeDebugger + + def initialize(executable, pid, options) + super(executable, pid, options) end - def gdb.update_threads + def set_flags + execute 'set scheduler-locking off' # we will deadlock with it + execute 'set unwindonsignal on' # in case of some signal we will exit gdb + end + + def update_threads process_threads = [] - info_threads = (self.execute 'info threads').split("\n") - # first line of gdb's response is ` Id Target Id Frame` info line - # last line of gdb's response is `(gdb) ` - info_threads.shift - info_threads.pop + info_threads = (execute 'info threads').split("\n") # each thread info looks like this: # 3 Thread 0x7ff535405700 (LWP 8291) "ruby-timer-thr" 0x00007ff534a15fdd in poll () at ../sysdeps/unix/syscall-template.S:81 info_threads.each do |thread_info| next unless thread_info =~ /[\s*]*\d+\s+Thread.*/ - $stderr.puts "thread_info: #{thread_info}" + $stdout.puts "thread_info: #{thread_info}" is_main = thread_info[0] == '*' - thread_info.sub!(/[\s*]*/, '') - thread_info.sub!(/\s.*$/, '') - thread = ProcessThread.new(thread_info.to_i, is_main) + thread_num = thread_info.sub(/[\s*]*/, '').sub(/\s.*$/, '').to_i + thread = ProcessThread.new(thread_num, is_main, self) if thread.is_main - $main_thread = thread + @main_thread = thread end process_threads << thread end process_threads end - def gdb.get_response - content = '' - loop do - sleep 0.01 # give time to gdb to finish command execution and print it to file - file = File.open($gdb_tmp_file, 'r') - content = file.read - file.close - break if content =~ /\(gdb\)\s\z/ - end - content + def check_already_under_debug + threads = execute 'info threads' + threads =~ /ruby-debug-ide/ end - def gdb.enable_logging - self.puts 'set logging on' + def switch_to_thread(thread_num) + execute "thread #{thread_num}" end - def gdb.disable_logging - self.puts 'set logging off' + def call_start_attach + super() + execute "call dlopen(\"/home/equi/Job/JetBrains/Internship_2016/ruby-debug-ide/ext/libAttach.so\", 2)" + execute "call start_attach(\"require '#{$path_to_debugger_loader}'; load_debugger(#{$gems_to_include.gsub("\"", "'")}, #{$argv.gsub("\"", "'")})\")" end - def gdb.overwrite_file - disable_logging - enable_logging + def to_s + 'gdb' end - def gdb.execute(command) - self.overwrite_file - self.puts command - $stdout.puts "executed command '#{command}' inside gdb." - if command == 'q' - return '' - end - response = self.get_response - if command == 'bt' - $last_bt = response - end - return response +end + +class ProcessThread + + attr_reader :thread_num, :is_main + + def initialize(thread_num, is_main, native_debugger) + @thread_num = thread_num + @is_main = is_main + @native_debugger = native_debugger end - def gdb.finish - $stdout.puts 'trying to finish current frame.' - self.execute 'finish' + def switch + @native_debugger.switch_to_thread(thread_num) end - def gdb.set_logging - self.puts "set logging file #{$gdb_tmp_file}" - self.puts 'set logging overwrite on' - self.puts 'set logging redirect on' - self.enable_logging + def finish + @native_debugger.execute 'finish' + end - $stdout.puts "all gdb output redirected to #{$gdb_tmp_file}." + def get_bt + @native_debugger.execute 'bt' end - def gdb.check_already_under_debug - threads = self.execute 'info threads' - return threads =~ /ruby-debug-ide/ + def any_caller_match(bt, pattern) + bt =~ /#{pattern}/ end - gdb.set_logging + def is_inside_malloc(bt = get_bt) + if any_caller_match(bt, '(malloc\.c)') + $stderr.puts "process #{@native_debugger.pid} is currently inside malloc." + true + else + false + end + end - if gdb.check_already_under_debug - $stderr.puts "Process #{$pid} is already under debug" - gdb.execute 'q' + def is_inside_gc(bt = get_bt) + if any_caller_match(bt, '(gc\.c)') + $stderr.puts "process #{@native_debugger.pid} is currently in garbage collection phase." + true + else + false + end end - gdb.execute 'set scheduler-locking off' - gdb.execute 'set unwindonsignal on' + def need_finish_frame + bt = get_bt + is_inside_malloc(bt) || is_inside_gc(bt) + end - should_check_threads_state = true +end - while should_check_threads_state - should_check_threads_state = false - gdb.update_threads.each do |thread| - thread.switch - while thread.need_finish_frame - should_check_threads_state = true - thread.finish - end - end +def choose_debugger + if true + GDB.new(@options.ruby_path, @options.pid, '-nh -nx') + else + LLDB.new(@options.ruby_path, @options.pid, '--no-lldbinit') end +end - $main_thread.switch +gdb = choose_debugger +gdb.attach_to_process +gdb.set_flags - gdb.execute "call dlopen(\"/home/equi/Job/JetBrains/Internship_2016/ruby-debug-ide/ext/libAttach.so\", 2)" - gdb.execute "call start_attach(\"require '#{path_to_debugger_loader}'; load_debugger(#{gems_to_include.gsub("\"", "'")}, #{argv.gsub("\"", "'")})\")" +if gdb.check_already_under_debug + $stderr.puts "Process #{gdb.pid} is already under debug" + gdb.exit + exit! +end - gdb_executed_all_commands = true - gdb.execute 'q' +should_check_threads_state = true +while should_check_threads_state + should_check_threads_state = false + gdb.update_threads.each do |thread| + thread.switch + while thread.need_finish_frame + should_check_threads_state = true + thread.finish + end + end end +gdb.call_start_attach +gdb.set_tbreak('__func_to_set_breakpoint_at') +gdb.continue +gdb.execute "call rb_eval_string_protect(\"require '#{$path_to_debugger_loader}'; load_debugger(#{$gems_to_include.gsub("\"", "'")}, #{$argv.gsub("\"", "'")})\", (int *)0)" +gdb.exit + trap('INT') do - unless gdb_executed_all_commands - $stderr.puts "Seems like could not attach to process. Its backtrace:\n#{$last_bt}" - $stderr.flush - end - exit 1 + $stderr.puts "Last backtrace:\n#{gdb.last_bt}" + exit! end -if options.uid - Process::Sys.setuid(options.uid.to_i) +if @options.uid + Process::Sys.setuid(@options.uid.to_i) end sleep diff --git a/ext/do_attach.c b/ext/do_attach.c index 7291b48..c29c9f6 100644 --- a/ext/do_attach.c +++ b/ext/do_attach.c @@ -12,6 +12,11 @@ __check_gc(void) return 0; } +static void +__func_to_set_breakpoint_at() +{ +} + static void __catch_line_event(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE klass) { @@ -23,15 +28,18 @@ __catch_line_event(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE rb_remove_event_hook(__catch_line_event); if (__check_gc()) return; - rb_eval_string_protect(_command_to_eval, NULL); // TODO pass something more useful than NULL + __func_to_set_breakpoint_at(); +// rb_eval_string_protect(_command_to_eval, NULL); // TODO pass something more useful than NULL } -void +int start_attach(const char* command) { + rb_eval_string("puts 'bla bla bla'"); _command_to_eval = command; if (__check_gc()) - return; + return 1; rb_global_variable((VALUE *) _command_to_eval); rb_add_event_hook(__catch_line_event, RUBY_EVENT_LINE, (VALUE) NULL); + return 2323; } diff --git a/ext/do_attach.h b/ext/do_attach.h index 3697d3f..5886935 100644 --- a/ext/do_attach.h +++ b/ext/do_attach.h @@ -5,6 +5,6 @@ #include #include -void start_attach(const char *command); +int start_attach(const char *command); #endif //__DO_ATTACH_H__ \ No newline at end of file From 36d3686d31106024082d6855bfdfd3c2cdfbe663 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Sun, 4 Sep 2016 22:07:20 +0300 Subject: [PATCH 015/104] some cosmetic changes + choosing with debugger to use --- bin/gdb_wrapper | 82 ++++++++++++++++++++++++++++++------------------- ext/do_attach.c | 10 ++---- ext/do_attach.h | 2 +- 3 files changed, 53 insertions(+), 41 deletions(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index 5482744..18c1c3e 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -59,19 +59,19 @@ end require 'ruby-debug-ide/greeter' Debugger::print_greeting_msg(nil, nil) -# this class is some kind of "interface" class NativeDebugger - attr_reader :pid, :last_bt + attr_reader :pid, :main_thread, :process_threads # @param executable -- path to ruby interpreter # @param pid -- pid of process you want to debug # @param options -- flags you want to specify to your debugger as a string (e.g. "-nx -nh" for gdb to disable .gdbinit) def initialize(executable, pid, options) @pid = pid - @main_thread = nil @delimiter = '__OUTPUT_FINISHED__' # for getting response - @last_bt = '' # to shaw it to user + @tbreak = '__func_to_set_breakpoint_at' + @main_thread = nil + @process_threads = nil launch_string = "#{self} #{executable} #{options}" @pipe = IO.popen(launch_string, 'r+') @@ -90,9 +90,6 @@ class NativeDebugger return '' end response = get_response - if command == 'bt' - @last_bt = response - end $stdout.puts "response for #{command}:\n#{response}\n\n\n\n" response end @@ -106,7 +103,7 @@ class NativeDebugger line = @pipe.readline $stderr.puts line next if line =~ /\(lldb\)/ # lldb repeats your input to its output - break if line =~ /\$\d+\s=\s"__OUTPUT_FINISHED__"/ + break if line =~ /\$\d+\s=\s"#{@delimiter}"/ content += line end content @@ -129,10 +126,12 @@ class NativeDebugger end def continue + $stdout.puts 'continuing' @pipe.puts 'c' loop do line = @pipe.readline - break if line =~ /__func_to_set_breakpoint_at/ + $stderr.puts line + break if line =~ /#{Regexp.escape(@tbreak)}/ end get_response end @@ -142,6 +141,15 @@ class NativeDebugger @main_thread.switch end + def wait_line_event + call_start_attach + continue + end + + def load_debugger + execute "call rb_eval_string_protect(\"require '#{$path_to_debugger_loader}'; load_debugger(#{$gems_to_include.gsub("\"", "'")}, #{$argv.gsub("\"", "'")})\", (int *)0)" + end + def exit execute 'q' end @@ -163,20 +171,20 @@ class LLDB < NativeDebugger end def update_threads - process_threads = [] + @process_threads = [] info_threads = (execute 'thread list').split("\n") info_threads.each do |thread_info| next unless thread_info =~ /[\s*]*thread\s#\d+.*/ $stdout.puts "thread_info: #{thread_info}" is_main = thread_info[0] == '*' thread_num = thread_info.sub(/[\s*]*thread\s#/, '').sub(/:\s.*$/, '').to_i - thread = ProcessThread.new(thread_num, is_main, self) + thread = ProcessThread.new(thread_num, is_main, thread_info, self) if thread.is_main @main_thread = thread end - process_threads << thread + @process_threads << thread end - process_threads + @process_threads end def check_already_under_debug @@ -191,11 +199,12 @@ class LLDB < NativeDebugger def call_start_attach super() execute "expr (void *) dlopen(\"/home/equi/Job/JetBrains/Internship_2016/ruby-debug-ide/ext/libAttach.so\", 2)" - execute "call start_attach(\"require '#{$path_to_debugger_loader}'; load_debugger(#{$gems_to_include.gsub("\"", "'")}, #{$argv.gsub("\"", "'")})\")" + execute 'call start_attach()' + set_tbreak(@tbreak) end def to_s - 'lldb-3.8' + 'lldb' end end @@ -212,22 +221,20 @@ class GDB < NativeDebugger end def update_threads - process_threads = [] + @process_threads = [] info_threads = (execute 'info threads').split("\n") - # each thread info looks like this: - # 3 Thread 0x7ff535405700 (LWP 8291) "ruby-timer-thr" 0x00007ff534a15fdd in poll () at ../sysdeps/unix/syscall-template.S:81 info_threads.each do |thread_info| next unless thread_info =~ /[\s*]*\d+\s+Thread.*/ $stdout.puts "thread_info: #{thread_info}" is_main = thread_info[0] == '*' thread_num = thread_info.sub(/[\s*]*/, '').sub(/\s.*$/, '').to_i - thread = ProcessThread.new(thread_num, is_main, self) + thread = ProcessThread.new(thread_num, is_main, thread_info, self) if thread.is_main @main_thread = thread end - process_threads << thread + @process_threads << thread end - process_threads + @process_threads end def check_already_under_debug @@ -242,7 +249,8 @@ class GDB < NativeDebugger def call_start_attach super() execute "call dlopen(\"/home/equi/Job/JetBrains/Internship_2016/ruby-debug-ide/ext/libAttach.so\", 2)" - execute "call start_attach(\"require '#{$path_to_debugger_loader}'; load_debugger(#{$gems_to_include.gsub("\"", "'")}, #{$argv.gsub("\"", "'")})\")" + execute 'call start_attach()' + set_tbreak(@tbreak) end def to_s @@ -253,12 +261,14 @@ end class ProcessThread - attr_reader :thread_num, :is_main + attr_reader :thread_num, :is_main, :thread_info, :last_bt - def initialize(thread_num, is_main, native_debugger) + def initialize(thread_num, is_main, thread_info, native_debugger) @thread_num = thread_num @is_main = is_main @native_debugger = native_debugger + @thread_info = thread_info + @last_bt = nil end def switch @@ -270,7 +280,7 @@ class ProcessThread end def get_bt - @native_debugger.execute 'bt' + @last_bt = @native_debugger.execute 'bt' end def any_caller_match(bt, pattern) @@ -302,11 +312,19 @@ class ProcessThread end +def exists_command(command) + `command -v #{command} >/dev/null 2>&1 || { exit 1; }` + $?.exitstatus == 0 +end + def choose_debugger - if true + $stderr.puts "exists: #{exists_command('gdb')}, #{exists_command('lldb')}" + if exists_command('gdb') GDB.new(@options.ruby_path, @options.pid, '-nh -nx') - else + elsif exists_command('lldb') LLDB.new(@options.ruby_path, @options.pid, '--no-lldbinit') + else + raise 'Neither gdb nor lldb was found. Aborting.' end end @@ -333,14 +351,14 @@ while should_check_threads_state end end -gdb.call_start_attach -gdb.set_tbreak('__func_to_set_breakpoint_at') -gdb.continue -gdb.execute "call rb_eval_string_protect(\"require '#{$path_to_debugger_loader}'; load_debugger(#{$gems_to_include.gsub("\"", "'")}, #{$argv.gsub("\"", "'")})\", (int *)0)" +gdb.wait_line_event +gdb.load_debugger gdb.exit trap('INT') do - $stderr.puts "Last backtrace:\n#{gdb.last_bt}" + gdb.process_threads.each do |thread| + $stderr.puts "Last backtrace for thread #{thread.thread_info}:\n#{thread.last_bt}" + end exit! end diff --git a/ext/do_attach.c b/ext/do_attach.c index c29c9f6..6778efc 100644 --- a/ext/do_attach.c +++ b/ext/do_attach.c @@ -1,7 +1,5 @@ #include "do_attach.h" -static const char *_command_to_eval; - static int __check_gc(void) { @@ -29,17 +27,13 @@ __catch_line_event(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE if (__check_gc()) return; __func_to_set_breakpoint_at(); -// rb_eval_string_protect(_command_to_eval, NULL); // TODO pass something more useful than NULL } int -start_attach(const char* command) +start_attach() { - rb_eval_string("puts 'bla bla bla'"); - _command_to_eval = command; if (__check_gc()) return 1; - rb_global_variable((VALUE *) _command_to_eval); rb_add_event_hook(__catch_line_event, RUBY_EVENT_LINE, (VALUE) NULL); - return 2323; + return 0; } diff --git a/ext/do_attach.h b/ext/do_attach.h index 5886935..e9e5603 100644 --- a/ext/do_attach.h +++ b/ext/do_attach.h @@ -5,6 +5,6 @@ #include #include -int start_attach(const char *command); +int start_attach(); #endif //__DO_ATTACH_H__ \ No newline at end of file From 096cd724eface3178d38320dd0c0d7e040bf307d Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Mon, 5 Sep 2016 15:07:48 +0300 Subject: [PATCH 016/104] printing backtrace of all threads in case of fail + some minor chages --- bin/gdb_wrapper | 66 +++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index 18c1c3e..abe68ba 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -61,7 +61,7 @@ Debugger::print_greeting_msg(nil, nil) class NativeDebugger - attr_reader :pid, :main_thread, :process_threads + attr_reader :pid, :main_thread, :process_threads, :pipe # @param executable -- path to ruby interpreter # @param pid -- pid of process you want to debug @@ -89,9 +89,7 @@ class NativeDebugger @pipe.close return '' end - response = get_response - $stdout.puts "response for #{command}:\n#{response}\n\n\n\n" - response + get_response end def get_response @@ -101,7 +99,6 @@ class NativeDebugger content = '' loop do line = @pipe.readline - $stderr.puts line next if line =~ /\(lldb\)/ # lldb repeats your input to its output break if line =~ /\$\d+\s=\s"#{@delimiter}"/ content += line @@ -152,6 +149,7 @@ class NativeDebugger def exit execute 'q' + @pipe.close end def to_s @@ -312,29 +310,44 @@ class ProcessThread end -def exists_command(command) +def command_exists(command) `command -v #{command} >/dev/null 2>&1 || { exit 1; }` $?.exitstatus == 0 end def choose_debugger - $stderr.puts "exists: #{exists_command('gdb')}, #{exists_command('lldb')}" - if exists_command('gdb') - GDB.new(@options.ruby_path, @options.pid, '-nh -nx') - elsif exists_command('lldb') - LLDB.new(@options.ruby_path, @options.pid, '--no-lldbinit') + if command_exists('gdb') + debugger = GDB.new(@options.ruby_path, @options.pid, '-nh -nx') + elsif command_exists('lldb') + debugger = LLDB.new(@options.ruby_path, @options.pid, '--no-lldbinit') else raise 'Neither gdb nor lldb was found. Aborting.' end + + trap('INT') do + unless debugger.pipe.closed? + $stderr.puts "backtraces for threads:\n\n" + debugger.process_threads.each do |thread| + $stderr.puts "#{thread.thread_info}\n#{thread.last_bt}\n\n" + end + end + exit! + end + + debugger end -gdb = choose_debugger -gdb.attach_to_process -gdb.set_flags +debugger = choose_debugger +debugger.attach_to_process +debugger.set_flags -if gdb.check_already_under_debug - $stderr.puts "Process #{gdb.pid} is already under debug" - gdb.exit +if @options.uid + Process::Sys.setuid(@options.uid.to_i) +end + +if debugger.check_already_under_debug + $stderr.puts "Process #{debugger.pid} is already under debug" + debugger.exit exit! end @@ -342,7 +355,7 @@ should_check_threads_state = true while should_check_threads_state should_check_threads_state = false - gdb.update_threads.each do |thread| + debugger.update_threads.each do |thread| thread.switch while thread.need_finish_frame should_check_threads_state = true @@ -351,19 +364,8 @@ while should_check_threads_state end end -gdb.wait_line_event -gdb.load_debugger -gdb.exit - -trap('INT') do - gdb.process_threads.each do |thread| - $stderr.puts "Last backtrace for thread #{thread.thread_info}:\n#{thread.last_bt}" - end - exit! -end - -if @options.uid - Process::Sys.setuid(@options.uid.to_i) -end +debugger.wait_line_event +debugger.load_debugger +debugger.exit sleep From c0668953ac07df8addb5158d96728d375145e5a2 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Tue, 6 Sep 2016 11:11:39 +0300 Subject: [PATCH 017/104] little refactoring. deleted unused functions. --- bin/gdb_wrapper | 1 - ext/Makefile | 4 ++-- ext/{do_attach.c => attach.c} | 16 +++------------- ext/attach.h | 9 +++++++++ ext/do_attach.h | 10 ---------- 5 files changed, 14 insertions(+), 26 deletions(-) rename ext/{do_attach.c => attach.c} (64%) create mode 100644 ext/attach.h delete mode 100644 ext/do_attach.h diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index abe68ba..308dacd 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -127,7 +127,6 @@ class NativeDebugger @pipe.puts 'c' loop do line = @pipe.readline - $stderr.puts line break if line =~ /#{Regexp.escape(@tbreak)}/ end get_response diff --git a/ext/Makefile b/ext/Makefile index 9bec56c..6c52c90 100644 --- a/ext/Makefile +++ b/ext/Makefile @@ -3,8 +3,8 @@ all: libAttach.so libAttach.so: libAttach.o gcc -shared -o libAttach.so libAttach.o -libAttach.o: do_attach.c - gcc -Wall -g -fPIC -c -I/home/equi/.rvm/rubies/ruby-2.3.1/include/ruby-2.3.0 -I/home/equi/.rvm/rubies/ruby-2.3.1/include/ruby-2.3.0/x86_64-linux/ do_attach.c -o libAttach.o +libAttach.o: attach.c + gcc -Wall -g -fPIC -c -I/home/equi/.rvm/rubies/ruby-2.3.1/include/ruby-2.3.0 -I/home/equi/.rvm/rubies/ruby-2.3.1/include/ruby-2.3.0/x86_64-linux/ attach.c -o libAttach.o clean: rm libAttach.* diff --git a/ext/do_attach.c b/ext/attach.c similarity index 64% rename from ext/do_attach.c rename to ext/attach.c index 6778efc..d4d4dbe 100644 --- a/ext/do_attach.c +++ b/ext/attach.c @@ -1,14 +1,4 @@ -#include "do_attach.h" - -static int -__check_gc(void) -{ - if (rb_during_gc()) { - fprintf(stderr, "Can not connect during garbage collection phase. Please, try again later.\n"); - return 1; - } - return 0; -} +#include "attach.h" static void __func_to_set_breakpoint_at() @@ -24,7 +14,7 @@ __catch_line_event(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE (void)sizeof(klass); rb_remove_event_hook(__catch_line_event); - if (__check_gc()) + if (rb_during_gc()) return; __func_to_set_breakpoint_at(); } @@ -32,7 +22,7 @@ __catch_line_event(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE int start_attach() { - if (__check_gc()) + if (rb_during_gc()) return 1; rb_add_event_hook(__catch_line_event, RUBY_EVENT_LINE, (VALUE) NULL); return 0; diff --git a/ext/attach.h b/ext/attach.h new file mode 100644 index 0000000..9c98192 --- /dev/null +++ b/ext/attach.h @@ -0,0 +1,9 @@ +#ifndef __ATTACH_H__ +#define __ATTACH_H__ + +#include +#include + +int start_attach(); + +#endif //__ATTACH_H__ \ No newline at end of file diff --git a/ext/do_attach.h b/ext/do_attach.h deleted file mode 100644 index e9e5603..0000000 --- a/ext/do_attach.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __DO_ATTACH_H__ -#define __DO_ATTACH_H__ - -#include -#include -#include - -int start_attach(); - -#endif //__DO_ATTACH_H__ \ No newline at end of file From cede1279387a54c42002b5a5eb623173f29baaf8 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Wed, 7 Sep 2016 14:59:38 +0300 Subject: [PATCH 018/104] finding attach.so file in debase added --- bin/gdb_wrapper | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index 308dacd..dbe2f2c 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -6,7 +6,7 @@ require 'ostruct' $stdout.sync = true $stderr.sync = true -@options = OpenStruct.new( +$options = OpenStruct.new( 'pid' => nil, 'sdk_path' => nil, 'uid' => nil, @@ -20,39 +20,39 @@ Some useful banner. EOB opts.on('--pid PID', 'pid of process you want to attach to for debugging') do |pid| - @options.pid = pid + $options.pid = pid end opts.on('--ruby-path RUBY_PATH', 'path to ruby interpreter') do |ruby_path| - @options.ruby_path = ruby_path + $options.ruby_path = ruby_path end opts.on('--uid UID', 'uid which this process should set after executing gdb attach') do |uid| - @options.uid = uid + $options.uid = uid end opts.on('--include-gem GEM_LIB_PATH', 'lib of gem to include') do |gem_lib_path| - @options.gems_to_include << gem_lib_path + $options.gems_to_include << gem_lib_path end end opts.parse! ARGV -unless @options.pid +unless $options.pid $stderr.puts 'You should specify PID of process you want to attach to' exit 1 end -unless @options.ruby_path +unless $options.ruby_path $stderr.puts 'You should specify path to the ruby interpreter' exit 1 end $argv = '["' + ARGV * '", "' + '"]' -$gems_to_include = '["' + @options.gems_to_include * '", "' + '"]' +$gems_to_include = '["' + $options.gems_to_include * '", "' + '"]' $path_to_debugger_loader = File.expand_path(File.dirname(__FILE__)) + '/../lib/ruby-debug-ide/attach/debugger_loader' -@options.gems_to_include.each do |gem_path| +$options.gems_to_include.each do |gem_path| $LOAD_PATH.unshift(gem_path) unless $LOAD_PATH.include?(gem_path) end @@ -72,6 +72,11 @@ class NativeDebugger @tbreak = '__func_to_set_breakpoint_at' @main_thread = nil @process_threads = nil + debase_path = $options.gems_to_include.select {|gem_path| gem_path =~ /debase/} + if debase_path.size == 0 + raise 'No debase gem found.' + end + @path_to_attach = debase_path[0] + '/attach.so' launch_string = "#{self} #{executable} #{options}" @pipe = IO.popen(launch_string, 'r+') @@ -195,7 +200,7 @@ class LLDB < NativeDebugger def call_start_attach super() - execute "expr (void *) dlopen(\"/home/equi/Job/JetBrains/Internship_2016/ruby-debug-ide/ext/libAttach.so\", 2)" + execute "expr (void *) dlopen(\"#{@path_to_attach}\", 2)" execute 'call start_attach()' set_tbreak(@tbreak) end @@ -245,7 +250,7 @@ class GDB < NativeDebugger def call_start_attach super() - execute "call dlopen(\"/home/equi/Job/JetBrains/Internship_2016/ruby-debug-ide/ext/libAttach.so\", 2)" + execute "call dlopen(\"#{@path_to_attach}\", 2)" execute 'call start_attach()' set_tbreak(@tbreak) end @@ -316,9 +321,9 @@ end def choose_debugger if command_exists('gdb') - debugger = GDB.new(@options.ruby_path, @options.pid, '-nh -nx') + debugger = GDB.new($options.ruby_path, $options.pid, '-nh -nx') elsif command_exists('lldb') - debugger = LLDB.new(@options.ruby_path, @options.pid, '--no-lldbinit') + debugger = LLDB.new($options.ruby_path, $options.pid, '--no-lldbinit') else raise 'Neither gdb nor lldb was found. Aborting.' end @@ -340,8 +345,8 @@ debugger = choose_debugger debugger.attach_to_process debugger.set_flags -if @options.uid - Process::Sys.setuid(@options.uid.to_i) +if $options.uid + Process::Sys.setuid($options.uid.to_i) end if debugger.check_already_under_debug From ea4af282cc07eb909c04f2700456af36a809876f Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Fri, 9 Sep 2016 21:29:51 +0300 Subject: [PATCH 019/104] bug fix: no need to close already closed stream (it works well only starting from ruby 2.3) --- bin/gdb_wrapper | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index dbe2f2c..d20f700 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -91,7 +91,6 @@ class NativeDebugger @pipe.puts command $stdout.puts "executed `#{command}` command inside #{self}." if command == 'q' - @pipe.close return '' end get_response From 7a15facd8663de89b4cbfa961b43ab6c255272a3 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Mon, 12 Sep 2016 00:54:50 +0300 Subject: [PATCH 020/104] replaced global variables with local ones --- bin/gdb_wrapper | 55 ++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index d20f700..08c37d2 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -6,7 +6,7 @@ require 'ostruct' $stdout.sync = true $stderr.sync = true -$options = OpenStruct.new( +options = OpenStruct.new( 'pid' => nil, 'sdk_path' => nil, 'uid' => nil, @@ -20,39 +20,38 @@ Some useful banner. EOB opts.on('--pid PID', 'pid of process you want to attach to for debugging') do |pid| - $options.pid = pid + options.pid = pid end opts.on('--ruby-path RUBY_PATH', 'path to ruby interpreter') do |ruby_path| - $options.ruby_path = ruby_path + options.ruby_path = ruby_path end opts.on('--uid UID', 'uid which this process should set after executing gdb attach') do |uid| - $options.uid = uid + options.uid = uid end opts.on('--include-gem GEM_LIB_PATH', 'lib of gem to include') do |gem_lib_path| - $options.gems_to_include << gem_lib_path + options.gems_to_include << gem_lib_path end end opts.parse! ARGV -unless $options.pid +unless options.pid $stderr.puts 'You should specify PID of process you want to attach to' exit 1 end -unless $options.ruby_path +unless options.ruby_path $stderr.puts 'You should specify path to the ruby interpreter' exit 1 end -$argv = '["' + ARGV * '", "' + '"]' -$gems_to_include = '["' + $options.gems_to_include * '", "' + '"]' -$path_to_debugger_loader = File.expand_path(File.dirname(__FILE__)) + '/../lib/ruby-debug-ide/attach/debugger_loader' +argv = '["' + ARGV * '", "' + '"]' +debugger_loader_path = File.expand_path(File.dirname(__FILE__)) + '/../lib/ruby-debug-ide/attach/debugger_loader' -$options.gems_to_include.each do |gem_path| +options.gems_to_include.each do |gem_path| $LOAD_PATH.unshift(gem_path) unless $LOAD_PATH.include?(gem_path) end @@ -65,20 +64,24 @@ class NativeDebugger # @param executable -- path to ruby interpreter # @param pid -- pid of process you want to debug - # @param options -- flags you want to specify to your debugger as a string (e.g. "-nx -nh" for gdb to disable .gdbinit) - def initialize(executable, pid, options) + # @param flags -- flags you want to specify to your debugger as a string (e.g. "-nx -nh" for gdb to disable .gdbinit) + def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, argv) @pid = pid @delimiter = '__OUTPUT_FINISHED__' # for getting response @tbreak = '__func_to_set_breakpoint_at' @main_thread = nil @process_threads = nil - debase_path = $options.gems_to_include.select {|gem_path| gem_path =~ /debase/} + debase_path = gems_to_include.select {|gem_path| gem_path =~ /debase/} if debase_path.size == 0 raise 'No debase gem found.' end @path_to_attach = debase_path[0] + '/attach.so' - launch_string = "#{self} #{executable} #{options}" + @gems_to_include = '["' + gems_to_include * '", "' + '"]' + @debugger_loader_path = debugger_loader_path + @argv = argv + + launch_string = "#{self} #{executable} #{flags}" @pipe = IO.popen(launch_string, 'r+') $stdout.puts "executed '#{launch_string}'" end @@ -147,7 +150,7 @@ class NativeDebugger end def load_debugger - execute "call rb_eval_string_protect(\"require '#{$path_to_debugger_loader}'; load_debugger(#{$gems_to_include.gsub("\"", "'")}, #{$argv.gsub("\"", "'")})\", (int *)0)" + execute "call rb_eval_string_protect(\"require '#{@debugger_loader_path}'; load_debugger(#{@gems_to_include.gsub("\"", "'")}, #{@argv.gsub("\"", "'")})\", (int *)0)" end def exit @@ -163,8 +166,8 @@ end class LLDB < NativeDebugger - def initialize(executable, pid, options) - super(executable, pid, options) + def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, argv) + super(executable, pid, flags, gems_to_include, debugger_loader_path, argv) end def set_flags @@ -212,8 +215,8 @@ end class GDB < NativeDebugger - def initialize(executable, pid, options) - super(executable, pid, options) + def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, argv) + super(executable, pid, flags, gems_to_include, debugger_loader_path, argv) end def set_flags @@ -318,11 +321,11 @@ def command_exists(command) $?.exitstatus == 0 end -def choose_debugger +def choose_debugger(ruby_path, pid, gems_to_include, debugger_loader_path, argv) if command_exists('gdb') - debugger = GDB.new($options.ruby_path, $options.pid, '-nh -nx') + debugger = GDB.new(ruby_path, pid, '-nh -nx', gems_to_include, debugger_loader_path, argv) elsif command_exists('lldb') - debugger = LLDB.new($options.ruby_path, $options.pid, '--no-lldbinit') + debugger = LLDB.new(ruby_path, pid, '--no-lldbinit', gems_to_include, debugger_loader_path, argv) else raise 'Neither gdb nor lldb was found. Aborting.' end @@ -340,12 +343,12 @@ def choose_debugger debugger end -debugger = choose_debugger +debugger = choose_debugger(options.ruby_path, options.pid, options.gems_to_include, debugger_loader_path, argv) debugger.attach_to_process debugger.set_flags -if $options.uid - Process::Sys.setuid($options.uid.to_i) +if options.uid + Process::Sys.setuid(options.uid.to_i) end if debugger.check_already_under_debug From d91c3d1fe742c19ecf53ec3059fb27e616870ca7 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Thu, 29 Sep 2016 22:30:51 +0300 Subject: [PATCH 021/104] added debug output if `--debug` key specified. --- bin/gdb_wrapper | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index 08c37d2..510d30f 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -13,6 +13,30 @@ options = OpenStruct.new( 'gems_to_include' => [] ) +module DebugPrinter + + class << self + attr_accessor :cli_debug + + def print_debug(msg) + if DebugPrinter.cli_debug + delimiter = '=' * 10 + upper_border = "\n#{delimiter}\n" + lower_border = "#{delimiter}\n\n" + + if msg.length > 0 and msg[msg.length - 1] != "\n" + lower_border = "\n" + lower_border + end + + $stdout.puts upper_border + msg + lower_border + end + end + end + +end + +DebugPrinter.cli_debug = ARGV.include? '--debug' + opts = OptionParser.new do |opts| # TODO need some banner opts.banner = </dev/null 2>&1 || { exit 1; }` + if $?.exitstatus != 0 + DebugPrinter.print_debug("#{checking_command}command does not exist.") + else + DebugPrinter.print_debug("#{checking_command}command does exist.") + end $?.exitstatus == 0 end @@ -348,6 +381,7 @@ debugger.attach_to_process debugger.set_flags if options.uid + DebugPrinter.print_debug("changing current uid from #{Process.uid} to #{options.uid}") Process::Sys.setuid(options.uid.to_i) end From 9026873991e19167cc7aef428b03eb3e97514b91 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Thu, 29 Sep 2016 22:50:00 +0300 Subject: [PATCH 022/104] It is essential for lldb connecting to OS X's ruby process to specify function type. Under OS X (due to compilation keys, I guess) lldb does not know type of ruby functions so we need to write them explicitly. --- bin/gdb_wrapper | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index 510d30f..d530811 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -105,6 +105,8 @@ class NativeDebugger @debugger_loader_path = debugger_loader_path @argv = argv + @eval_string = "rb_eval_string_protect(\"require '#{@debugger_loader_path}'; load_debugger(#{@gems_to_include.gsub("\"", "'")}, #{@argv.gsub("\"", "'")})\", (int *)0)" + launch_string = "#{self} #{executable} #{flags}" @pipe = IO.popen(launch_string, 'r+') $stdout.puts "executed '#{launch_string}'" @@ -177,7 +179,7 @@ class NativeDebugger end def load_debugger - execute "call rb_eval_string_protect(\"require '#{@debugger_loader_path}'; load_debugger(#{@gems_to_include.gsub("\"", "'")}, #{@argv.gsub("\"", "'")})\", (int *)0)" + end def exit @@ -230,10 +232,14 @@ class LLDB < NativeDebugger def call_start_attach super() execute "expr (void *) dlopen(\"#{@path_to_attach}\", 2)" - execute 'call start_attach()' + execute 'expr (int) start_attach()' set_tbreak(@tbreak) end + def load_debugger + execute "expr (VALUE) #{@eval_string}" + end + def to_s 'lldb' end @@ -284,6 +290,10 @@ class GDB < NativeDebugger set_tbreak(@tbreak) end + def load_debugger + execute "call #{@eval_string}" + end + def to_s 'gdb' end From 6ae52eef42c62ea3b2f443abb44dfbe7d314ab1a Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Thu, 29 Sep 2016 23:01:20 +0300 Subject: [PATCH 023/104] Gdb does not understand ruby symbol table under OS X (I guess it does not understand darwin debug format). That make it essential to prefer lldb over gdb (lldb works fine). --- bin/gdb_wrapper | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index d530811..84d7fe3 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -365,10 +365,10 @@ def command_exists(command) end def choose_debugger(ruby_path, pid, gems_to_include, debugger_loader_path, argv) - if command_exists('gdb') - debugger = GDB.new(ruby_path, pid, '-nh -nx', gems_to_include, debugger_loader_path, argv) - elsif command_exists('lldb') + if command_exists('lldb') debugger = LLDB.new(ruby_path, pid, '--no-lldbinit', gems_to_include, debugger_loader_path, argv) + elsif command_exists('gdb') + debugger = GDB.new(ruby_path, pid, '-nh -nx', gems_to_include, debugger_loader_path, argv) else raise 'Neither gdb nor lldb was found. Aborting.' end From c03250eabf1f7120f1118304afdb9a6eb1b50bff Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Thu, 29 Sep 2016 23:12:53 +0300 Subject: [PATCH 024/104] Attach lib can have different extension than '.so' (e.g. '.bundle' under OS X). So we need to find appropriate one manually. --- bin/gdb_wrapper | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index 84d7fe3..d222965 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -99,7 +99,7 @@ class NativeDebugger if debase_path.size == 0 raise 'No debase gem found.' end - @path_to_attach = debase_path[0] + '/attach.so' + @path_to_attach = find_attach_lib(debase_path[0]) @gems_to_include = '["' + gems_to_include * '", "' + '"]' @debugger_loader_path = debugger_loader_path @@ -112,6 +112,18 @@ class NativeDebugger $stdout.puts "executed '#{launch_string}'" end + def find_attach_lib(debase_path) + attach_lib = debase_path + '/attach' + known_extensions = %w(.so .bundle .dll) + known_extensions.each do |ext| + if File.file?(attach_lib + ext) + return attach_lib + ext + end + end + + raise 'Could not find attach library' + end + def attach_to_process execute "attach #{@pid}" end From f3955ca4f99990707ae8d240f9feda72c58d14cc Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Tue, 4 Oct 2016 14:54:04 +0300 Subject: [PATCH 025/104] fixes for lldb-3.6. `print` does not work there. output works bad after `thread select`. replaced debug output with more representative. --- bin/gdb_wrapper | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index d222965..4fdf002 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -20,15 +20,7 @@ module DebugPrinter def print_debug(msg) if DebugPrinter.cli_debug - delimiter = '=' * 10 - upper_border = "\n#{delimiter}\n" - lower_border = "#{delimiter}\n\n" - - if msg.length > 0 and msg[msg.length - 1] != "\n" - lower_border = "\n" + lower_border - end - - $stdout.puts upper_border + msg + lower_border + $stderr.puts msg end end end @@ -139,18 +131,17 @@ class NativeDebugger def get_response # we need this hack to understand that debugger gave us all output from last executed command - @pipe.puts "print \"#{@delimiter}\"" + print_delimiter content = '' loop do line = @pipe.readline + break if check_delimiter(line) + DebugPrinter.print_debug('respond line: ' + line) next if line =~ /\(lldb\)/ # lldb repeats your input to its output - break if line =~ /\$\d+\s=\s"#{@delimiter}"/ content += line end - DebugPrinter.print_debug(content) - content end @@ -162,6 +153,14 @@ class NativeDebugger end + def print_delimiter + + end + + def check_delimiter(line) + + end + def switch_to_thread end @@ -220,7 +219,6 @@ class LLDB < NativeDebugger info_threads = (execute 'thread list').split("\n") info_threads.each do |thread_info| next unless thread_info =~ /[\s*]*thread\s#\d+.*/ - $stdout.puts "thread_info: #{thread_info}" is_main = thread_info[0] == '*' thread_num = thread_info.sub(/[\s*]*thread\s#/, '').sub(/:\s.*$/, '').to_i thread = ProcessThread.new(thread_num, is_main, thread_info, self) @@ -248,6 +246,14 @@ class LLDB < NativeDebugger set_tbreak(@tbreak) end + def print_delimiter + @pipe.puts "script print \"#{@delimiter}\"" + end + + def check_delimiter(line) + line =~ /#{@delimiter}$/ + end + def load_debugger execute "expr (VALUE) #{@eval_string}" end @@ -302,6 +308,14 @@ class GDB < NativeDebugger set_tbreak(@tbreak) end + def print_delimiter + @pipe.puts "print \"#{@delimiter}\"" + end + + def check_delimiter(line) + line =~ /\$\d+\s=\s"#{@delimiter}"/ + end + def load_debugger execute "call #{@eval_string}" end From 705c66f1814e09ee9b3f7c58a6957aef32216133 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Mon, 10 Oct 2016 16:32:05 +0300 Subject: [PATCH 026/104] refactoring: separated debuggers and util methods --- bin/gdb_wrapper | 340 +------------------ lib/ruby-debug-ide/attach/gdb.rb | 69 ++++ lib/ruby-debug-ide/attach/lldb.rb | 67 ++++ lib/ruby-debug-ide/attach/native_debugger.rb | 129 +++++++ lib/ruby-debug-ide/attach/process_thread.rb | 54 +++ lib/ruby-debug-ide/attach/util.rb | 35 ++ 6 files changed, 357 insertions(+), 337 deletions(-) create mode 100644 lib/ruby-debug-ide/attach/gdb.rb create mode 100644 lib/ruby-debug-ide/attach/lldb.rb create mode 100644 lib/ruby-debug-ide/attach/native_debugger.rb create mode 100644 lib/ruby-debug-ide/attach/process_thread.rb create mode 100644 lib/ruby-debug-ide/attach/util.rb diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index 4fdf002..905e741 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -74,343 +74,9 @@ end require 'ruby-debug-ide/greeter' Debugger::print_greeting_msg(nil, nil) -class NativeDebugger - - attr_reader :pid, :main_thread, :process_threads, :pipe - - # @param executable -- path to ruby interpreter - # @param pid -- pid of process you want to debug - # @param flags -- flags you want to specify to your debugger as a string (e.g. "-nx -nh" for gdb to disable .gdbinit) - def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, argv) - @pid = pid - @delimiter = '__OUTPUT_FINISHED__' # for getting response - @tbreak = '__func_to_set_breakpoint_at' - @main_thread = nil - @process_threads = nil - debase_path = gems_to_include.select {|gem_path| gem_path =~ /debase/} - if debase_path.size == 0 - raise 'No debase gem found.' - end - @path_to_attach = find_attach_lib(debase_path[0]) - - @gems_to_include = '["' + gems_to_include * '", "' + '"]' - @debugger_loader_path = debugger_loader_path - @argv = argv - - @eval_string = "rb_eval_string_protect(\"require '#{@debugger_loader_path}'; load_debugger(#{@gems_to_include.gsub("\"", "'")}, #{@argv.gsub("\"", "'")})\", (int *)0)" - - launch_string = "#{self} #{executable} #{flags}" - @pipe = IO.popen(launch_string, 'r+') - $stdout.puts "executed '#{launch_string}'" - end - - def find_attach_lib(debase_path) - attach_lib = debase_path + '/attach' - known_extensions = %w(.so .bundle .dll) - known_extensions.each do |ext| - if File.file?(attach_lib + ext) - return attach_lib + ext - end - end - - raise 'Could not find attach library' - end - - def attach_to_process - execute "attach #{@pid}" - end - - def execute(command) - @pipe.puts command - $stdout.puts "executed `#{command}` command inside #{self}." - if command == 'q' - return '' - end - get_response - end - - def get_response - # we need this hack to understand that debugger gave us all output from last executed command - print_delimiter - - content = '' - loop do - line = @pipe.readline - break if check_delimiter(line) - DebugPrinter.print_debug('respond line: ' + line) - next if line =~ /\(lldb\)/ # lldb repeats your input to its output - content += line - end - - content - end - - def update_threads - - end - - def check_already_under_debug - - end - - def print_delimiter - - end - - def check_delimiter(line) - - end - - def switch_to_thread - - end - - def set_tbreak(str) - execute "tbreak #{str}" - end - - def continue - $stdout.puts 'continuing' - @pipe.puts 'c' - loop do - line = @pipe.readline - break if line =~ /#{Regexp.escape(@tbreak)}/ - end - get_response - end - - def call_start_attach - raise 'No main thread found. Did you forget to call `update_threads`?' if @main_thread == nil - @main_thread.switch - end - - def wait_line_event - call_start_attach - continue - end - - def load_debugger - - end - - def exit - execute 'q' - @pipe.close - end - - def to_s - 'native_debugger' - end - -end - -class LLDB < NativeDebugger - - def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, argv) - super(executable, pid, flags, gems_to_include, debugger_loader_path, argv) - end - - def set_flags - - end - - def update_threads - @process_threads = [] - info_threads = (execute 'thread list').split("\n") - info_threads.each do |thread_info| - next unless thread_info =~ /[\s*]*thread\s#\d+.*/ - is_main = thread_info[0] == '*' - thread_num = thread_info.sub(/[\s*]*thread\s#/, '').sub(/:\s.*$/, '').to_i - thread = ProcessThread.new(thread_num, is_main, thread_info, self) - if thread.is_main - @main_thread = thread - end - @process_threads << thread - end - @process_threads - end - - def check_already_under_debug - threads = execute 'thread list' - threads =~ /ruby-debug-ide/ - end - - def switch_to_thread(thread_num) - execute "thread select #{thread_num}" - end - - def call_start_attach - super() - execute "expr (void *) dlopen(\"#{@path_to_attach}\", 2)" - execute 'expr (int) start_attach()' - set_tbreak(@tbreak) - end - - def print_delimiter - @pipe.puts "script print \"#{@delimiter}\"" - end - - def check_delimiter(line) - line =~ /#{@delimiter}$/ - end - - def load_debugger - execute "expr (VALUE) #{@eval_string}" - end - - def to_s - 'lldb' - end - -end - -class GDB < NativeDebugger - - def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, argv) - super(executable, pid, flags, gems_to_include, debugger_loader_path, argv) - end - - def set_flags - execute 'set scheduler-locking off' # we will deadlock with it - execute 'set unwindonsignal on' # in case of some signal we will exit gdb - end - - def update_threads - @process_threads = [] - info_threads = (execute 'info threads').split("\n") - info_threads.each do |thread_info| - next unless thread_info =~ /[\s*]*\d+\s+Thread.*/ - $stdout.puts "thread_info: #{thread_info}" - is_main = thread_info[0] == '*' - thread_num = thread_info.sub(/[\s*]*/, '').sub(/\s.*$/, '').to_i - thread = ProcessThread.new(thread_num, is_main, thread_info, self) - if thread.is_main - @main_thread = thread - end - @process_threads << thread - end - @process_threads - end - - def check_already_under_debug - threads = execute 'info threads' - threads =~ /ruby-debug-ide/ - end - - def switch_to_thread(thread_num) - execute "thread #{thread_num}" - end - - def call_start_attach - super() - execute "call dlopen(\"#{@path_to_attach}\", 2)" - execute 'call start_attach()' - set_tbreak(@tbreak) - end - - def print_delimiter - @pipe.puts "print \"#{@delimiter}\"" - end - - def check_delimiter(line) - line =~ /\$\d+\s=\s"#{@delimiter}"/ - end - - def load_debugger - execute "call #{@eval_string}" - end - - def to_s - 'gdb' - end - -end - -class ProcessThread - - attr_reader :thread_num, :is_main, :thread_info, :last_bt - - def initialize(thread_num, is_main, thread_info, native_debugger) - @thread_num = thread_num - @is_main = is_main - @native_debugger = native_debugger - @thread_info = thread_info - @last_bt = nil - end - - def switch - @native_debugger.switch_to_thread(thread_num) - end - - def finish - @native_debugger.execute 'finish' - end - - def get_bt - @last_bt = @native_debugger.execute 'bt' - end - - def any_caller_match(bt, pattern) - bt =~ /#{pattern}/ - end - - def is_inside_malloc(bt = get_bt) - if any_caller_match(bt, '(malloc\.c)') - $stderr.puts "process #{@native_debugger.pid} is currently inside malloc." - true - else - false - end - end - - def is_inside_gc(bt = get_bt) - if any_caller_match(bt, '(gc\.c)') - $stderr.puts "process #{@native_debugger.pid} is currently in garbage collection phase." - true - else - false - end - end - - def need_finish_frame - bt = get_bt - is_inside_malloc(bt) || is_inside_gc(bt) - end - -end - -def command_exists(command) - checking_command = "checking command #{command} for existence\n" - `command -v #{command} >/dev/null 2>&1 || { exit 1; }` - if $?.exitstatus != 0 - DebugPrinter.print_debug("#{checking_command}command does not exist.") - else - DebugPrinter.print_debug("#{checking_command}command does exist.") - end - $?.exitstatus == 0 -end - -def choose_debugger(ruby_path, pid, gems_to_include, debugger_loader_path, argv) - if command_exists('lldb') - debugger = LLDB.new(ruby_path, pid, '--no-lldbinit', gems_to_include, debugger_loader_path, argv) - elsif command_exists('gdb') - debugger = GDB.new(ruby_path, pid, '-nh -nx', gems_to_include, debugger_loader_path, argv) - else - raise 'Neither gdb nor lldb was found. Aborting.' - end - - trap('INT') do - unless debugger.pipe.closed? - $stderr.puts "backtraces for threads:\n\n" - debugger.process_threads.each do |thread| - $stderr.puts "#{thread.thread_info}\n#{thread.last_bt}\n\n" - end - end - exit! - end - - debugger -end +require 'ruby-debug-ide/attach/util' +require 'ruby-debug-ide/attach/native_debugger' +require 'ruby-debug-ide/attach/process_thread' debugger = choose_debugger(options.ruby_path, options.pid, options.gems_to_include, debugger_loader_path, argv) debugger.attach_to_process diff --git a/lib/ruby-debug-ide/attach/gdb.rb b/lib/ruby-debug-ide/attach/gdb.rb new file mode 100644 index 0000000..a507880 --- /dev/null +++ b/lib/ruby-debug-ide/attach/gdb.rb @@ -0,0 +1,69 @@ +require 'ruby-debug-ide/attach/native_debugger' + +class GDB < NativeDebugger + + def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, argv) + super(executable, pid, flags, gems_to_include, debugger_loader_path, argv) + end + + def set_flags + execute 'set scheduler-locking off' # we will deadlock with it + execute 'set unwindonsignal on' # in case of some signal we will exit gdb + end + + def update_threads + @process_threads = [] + info_threads = (execute 'info threads').split("\n") + info_threads.each do |thread_info| + next unless thread_info =~ /[\s*]*\d+\s+Thread.*/ + $stdout.puts "thread_info: #{thread_info}" + is_main = thread_info[0] == '*' + thread_num = thread_info.sub(/[\s*]*/, '').sub(/\s.*$/, '').to_i + thread = ProcessThread.new(thread_num, is_main, thread_info, self) + if thread.is_main + @main_thread = thread + end + @process_threads << thread + end + @process_threads + end + + def check_already_under_debug + threads = execute 'info threads' + threads =~ /ruby-debug-ide/ + end + + def switch_to_thread(thread_num) + execute "thread #{thread_num}" + end + + def call_start_attach + super() + execute "call dlopen(\"#{@path_to_attach}\", 2)" + execute 'call start_attach()' + set_tbreak(@tbreak) + end + + def print_delimiter + @pipe.puts "print \"#{@delimiter}\"" + end + + def check_delimiter(line) + line =~ /\$\d+\s=\s"#{@delimiter}"/ + end + + def load_debugger + execute "call #{@eval_string}" + end + + def to_s + GDB.to_s + end + + class << self + def to_s + 'gdb' + end + end + +end diff --git a/lib/ruby-debug-ide/attach/lldb.rb b/lib/ruby-debug-ide/attach/lldb.rb new file mode 100644 index 0000000..87245df --- /dev/null +++ b/lib/ruby-debug-ide/attach/lldb.rb @@ -0,0 +1,67 @@ +require 'ruby-debug-ide/attach/native_debugger' + +class LLDB < NativeDebugger + + def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, argv) + super(executable, pid, flags, gems_to_include, debugger_loader_path, argv) + end + + def set_flags + + end + + def update_threads + @process_threads = [] + info_threads = (execute 'thread list').split("\n") + info_threads.each do |thread_info| + next unless thread_info =~ /[\s*]*thread\s#\d+.*/ + is_main = thread_info[0] == '*' + thread_num = thread_info.sub(/[\s*]*thread\s#/, '').sub(/:\s.*$/, '').to_i + thread = ProcessThread.new(thread_num, is_main, thread_info, self) + if thread.is_main + @main_thread = thread + end + @process_threads << thread + end + @process_threads + end + + def check_already_under_debug + threads = execute 'thread list' + threads =~ /ruby-debug-ide/ + end + + def switch_to_thread(thread_num) + execute "thread select #{thread_num}" + end + + def call_start_attach + super() + execute "expr (void *) dlopen(\"#{@path_to_attach}\", 2)" + execute 'expr (int) start_attach()' + set_tbreak(@tbreak) + end + + def print_delimiter + @pipe.puts "script print \"#{@delimiter}\"" + end + + def check_delimiter(line) + line =~ /#{@delimiter}$/ + end + + def load_debugger + execute "expr (VALUE) #{@eval_string}" + end + + def to_s + LLDB.to_s + end + + class << self + def to_s + 'lldb' + end + end + +end diff --git a/lib/ruby-debug-ide/attach/native_debugger.rb b/lib/ruby-debug-ide/attach/native_debugger.rb new file mode 100644 index 0000000..d48ed1e --- /dev/null +++ b/lib/ruby-debug-ide/attach/native_debugger.rb @@ -0,0 +1,129 @@ +class NativeDebugger + + attr_reader :pid, :main_thread, :process_threads, :pipe + + # @param executable -- path to ruby interpreter + # @param pid -- pid of process you want to debug + # @param flags -- flags you want to specify to your debugger as a string (e.g. "-nx -nh" for gdb to disable .gdbinit) + def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, argv) + @pid = pid + @delimiter = '__OUTPUT_FINISHED__' # for getting response + @tbreak = '__func_to_set_breakpoint_at' + @main_thread = nil + @process_threads = nil + debase_path = gems_to_include.select {|gem_path| gem_path =~ /debase/} + if debase_path.size == 0 + raise 'No debase gem found.' + end + @path_to_attach = find_attach_lib(debase_path[0]) + + @gems_to_include = '["' + gems_to_include * '", "' + '"]' + @debugger_loader_path = debugger_loader_path + @argv = argv + + @eval_string = "rb_eval_string_protect(\"require '#{@debugger_loader_path}'; load_debugger(#{@gems_to_include.gsub("\"", "'")}, #{@argv.gsub("\"", "'")})\", (int *)0)" + + launch_string = "#{self} #{executable} #{flags}" + @pipe = IO.popen(launch_string, 'r+') + $stdout.puts "executed '#{launch_string}'" + end + + def find_attach_lib(debase_path) + attach_lib = debase_path + '/attach' + known_extensions = %w(.so .bundle .dll) + known_extensions.each do |ext| + if File.file?(attach_lib + ext) + return attach_lib + ext + end + end + + raise 'Could not find attach library' + end + + def attach_to_process + execute "attach #{@pid}" + end + + def execute(command) + @pipe.puts command + $stdout.puts "executed `#{command}` command inside #{self}." + if command == 'q' + return '' + end + get_response + end + + def get_response + # we need this hack to understand that debugger gave us all output from last executed command + print_delimiter + + content = '' + loop do + line = @pipe.readline + break if check_delimiter(line) + DebugPrinter.print_debug('respond line: ' + line) + next if line =~ /\(lldb\)/ # lldb repeats your input to its output + content += line + end + + content + end + + def update_threads + + end + + def check_already_under_debug + + end + + def print_delimiter + + end + + def check_delimiter(line) + + end + + def switch_to_thread + + end + + def set_tbreak(str) + execute "tbreak #{str}" + end + + def continue + $stdout.puts 'continuing' + @pipe.puts 'c' + loop do + line = @pipe.readline + break if line =~ /#{Regexp.escape(@tbreak)}/ + end + get_response + end + + def call_start_attach + raise 'No main thread found. Did you forget to call `update_threads`?' if @main_thread == nil + @main_thread.switch + end + + def wait_line_event + call_start_attach + continue + end + + def load_debugger + + end + + def exit + execute 'q' + @pipe.close + end + + def to_s + 'native_debugger' + end + +end diff --git a/lib/ruby-debug-ide/attach/process_thread.rb b/lib/ruby-debug-ide/attach/process_thread.rb new file mode 100644 index 0000000..cee7ea7 --- /dev/null +++ b/lib/ruby-debug-ide/attach/process_thread.rb @@ -0,0 +1,54 @@ +require 'ruby-debug-ide/attach/native_debugger' + +class ProcessThread + + attr_reader :thread_num, :is_main, :thread_info, :last_bt + + def initialize(thread_num, is_main, thread_info, native_debugger) + @thread_num = thread_num + @is_main = is_main + @native_debugger = native_debugger + @thread_info = thread_info + @last_bt = nil + end + + def switch + @native_debugger.switch_to_thread(thread_num) + end + + def finish + @native_debugger.execute 'finish' + end + + def get_bt + @last_bt = @native_debugger.execute 'bt' + end + + def any_caller_match(bt, pattern) + bt =~ /#{pattern}/ + end + + def is_inside_malloc(bt = get_bt) + if any_caller_match(bt, '(malloc)') + $stderr.puts "process #{@native_debugger.pid} is currently inside malloc." + true + else + false + end + end + + def is_inside_gc(bt = get_bt) + if any_caller_match(bt, '(gc\.c)') + $stderr.puts "process #{@native_debugger.pid} is currently in garbage collection phase." + true + else + false + end + end + + def need_finish_frame + bt = get_bt + is_inside_malloc(bt) || is_inside_gc(bt) + end + +end diff --git a/lib/ruby-debug-ide/attach/util.rb b/lib/ruby-debug-ide/attach/util.rb new file mode 100644 index 0000000..035933c --- /dev/null +++ b/lib/ruby-debug-ide/attach/util.rb @@ -0,0 +1,35 @@ +require 'ruby-debug-ide/attach/lldb' +require 'ruby-debug-ide/attach/gdb' + +def command_exists(command) + checking_command = "checking command #{command} for existence\n" + `command -v #{command} >/dev/null 2>&1 || { exit 1; }` + if $?.exitstatus != 0 + DebugPrinter.print_debug("#{checking_command}command does not exist.") + else + DebugPrinter.print_debug("#{checking_command}command does exist.") + end + $?.exitstatus == 0 +end + +def choose_debugger(ruby_path, pid, gems_to_include, debugger_loader_path, argv) + if command_exists(LLDB.to_s) + debugger = LLDB.new(ruby_path, pid, '--no-lldbinit', gems_to_include, debugger_loader_path, argv) + elsif command_exists(GDB.to_s) + debugger = GDB.new(ruby_path, pid, '-nh -nx', gems_to_include, debugger_loader_path, argv) + else + raise 'Neither gdb nor lldb was found. Aborting.' + end + + trap('INT') do + unless debugger.pipe.closed? + $stderr.puts "backtraces for threads:\n\n" + debugger.process_threads.each do |thread| + $stderr.puts "#{thread.thread_info}\n#{thread.last_bt}\n\n" + end + end + exit! + end + + debugger +end From fa117ee53e8034629deb3e0e1aadb20313f1fb7f Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Mon, 10 Oct 2016 18:40:18 +0300 Subject: [PATCH 027/104] More debug output --- lib/ruby-debug-ide/attach/native_debugger.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/attach/native_debugger.rb b/lib/ruby-debug-ide/attach/native_debugger.rb index d48ed1e..4d9df48 100644 --- a/lib/ruby-debug-ide/attach/native_debugger.rb +++ b/lib/ruby-debug-ide/attach/native_debugger.rb @@ -60,8 +60,8 @@ def get_response content = '' loop do line = @pipe.readline - break if check_delimiter(line) DebugPrinter.print_debug('respond line: ' + line) + break if check_delimiter(line) next if line =~ /\(lldb\)/ # lldb repeats your input to its output content += line end @@ -98,6 +98,7 @@ def continue @pipe.puts 'c' loop do line = @pipe.readline + DebugPrinter.print_debug('respond line: ' + line) break if line =~ /#{Regexp.escape(@tbreak)}/ end get_response From 327fd704fe9bfd2afcfc2444cad5607df4ad835f Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Mon, 17 Oct 2016 15:18:58 +0300 Subject: [PATCH 028/104] More lldb-like breakpoint setting --- lib/ruby-debug-ide/attach/gdb.rb | 6 +++++- lib/ruby-debug-ide/attach/lldb.rb | 6 +++++- lib/ruby-debug-ide/attach/native_debugger.rb | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/ruby-debug-ide/attach/gdb.rb b/lib/ruby-debug-ide/attach/gdb.rb index a507880..1214816 100644 --- a/lib/ruby-debug-ide/attach/gdb.rb +++ b/lib/ruby-debug-ide/attach/gdb.rb @@ -37,11 +37,15 @@ def switch_to_thread(thread_num) execute "thread #{thread_num}" end + def set_break(str) + execute "tbreak #{str}" + end + def call_start_attach super() execute "call dlopen(\"#{@path_to_attach}\", 2)" execute 'call start_attach()' - set_tbreak(@tbreak) + set_break(@tbreak) end def print_delimiter diff --git a/lib/ruby-debug-ide/attach/lldb.rb b/lib/ruby-debug-ide/attach/lldb.rb index 87245df..5d301d2 100644 --- a/lib/ruby-debug-ide/attach/lldb.rb +++ b/lib/ruby-debug-ide/attach/lldb.rb @@ -35,11 +35,15 @@ def switch_to_thread(thread_num) execute "thread select #{thread_num}" end + def set_break(str) + execute "breakpoint set --shlib #{@path_to_attach} --name #{str}" + end + def call_start_attach super() execute "expr (void *) dlopen(\"#{@path_to_attach}\", 2)" execute 'expr (int) start_attach()' - set_tbreak(@tbreak) + set_break(@tbreak) end def print_delimiter diff --git a/lib/ruby-debug-ide/attach/native_debugger.rb b/lib/ruby-debug-ide/attach/native_debugger.rb index 4d9df48..852d5a4 100644 --- a/lib/ruby-debug-ide/attach/native_debugger.rb +++ b/lib/ruby-debug-ide/attach/native_debugger.rb @@ -89,8 +89,8 @@ def switch_to_thread end - def set_tbreak(str) - execute "tbreak #{str}" + def set_break(str) + end def continue From 68e87533a2268cf0bccc31f3b164e8125caee507 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Mon, 17 Oct 2016 15:22:35 +0300 Subject: [PATCH 029/104] Proper extension added --- lib/ruby-debug-ide/attach/native_debugger.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/attach/native_debugger.rb b/lib/ruby-debug-ide/attach/native_debugger.rb index 852d5a4..cda6876 100644 --- a/lib/ruby-debug-ide/attach/native_debugger.rb +++ b/lib/ruby-debug-ide/attach/native_debugger.rb @@ -30,7 +30,7 @@ def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, ar def find_attach_lib(debase_path) attach_lib = debase_path + '/attach' - known_extensions = %w(.so .bundle .dll) + known_extensions = %w(.so .bundle .dll .dylib) known_extensions.each do |ext| if File.file?(attach_lib + ext) return attach_lib + ext From dc009872a7073c608ce1e98a9f55068248a4e548 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Mon, 17 Oct 2016 15:28:11 +0300 Subject: [PATCH 030/104] C extension for attach already moved to debase gem --- ext/Makefile | 10 ---------- ext/attach.c | 29 ----------------------------- ext/attach.h | 9 --------- 3 files changed, 48 deletions(-) delete mode 100644 ext/Makefile delete mode 100644 ext/attach.c delete mode 100644 ext/attach.h diff --git a/ext/Makefile b/ext/Makefile deleted file mode 100644 index 6c52c90..0000000 --- a/ext/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -all: libAttach.so - -libAttach.so: libAttach.o - gcc -shared -o libAttach.so libAttach.o - -libAttach.o: attach.c - gcc -Wall -g -fPIC -c -I/home/equi/.rvm/rubies/ruby-2.3.1/include/ruby-2.3.0 -I/home/equi/.rvm/rubies/ruby-2.3.1/include/ruby-2.3.0/x86_64-linux/ attach.c -o libAttach.o - -clean: - rm libAttach.* diff --git a/ext/attach.c b/ext/attach.c deleted file mode 100644 index d4d4dbe..0000000 --- a/ext/attach.c +++ /dev/null @@ -1,29 +0,0 @@ -#include "attach.h" - -static void -__func_to_set_breakpoint_at() -{ -} - -static void -__catch_line_event(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE klass) -{ - (void)sizeof(evflag); - (void)sizeof(self); - (void)sizeof(mid); - (void)sizeof(klass); - - rb_remove_event_hook(__catch_line_event); - if (rb_during_gc()) - return; - __func_to_set_breakpoint_at(); -} - -int -start_attach() -{ - if (rb_during_gc()) - return 1; - rb_add_event_hook(__catch_line_event, RUBY_EVENT_LINE, (VALUE) NULL); - return 0; -} diff --git a/ext/attach.h b/ext/attach.h deleted file mode 100644 index 9c98192..0000000 --- a/ext/attach.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef __ATTACH_H__ -#define __ATTACH_H__ - -#include -#include - -int start_attach(); - -#endif //__ATTACH_H__ \ No newline at end of file From 17e92ecfd75f3f4c0829f5371705ad2b1e25afc2 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Fri, 28 Oct 2016 01:39:23 +0300 Subject: [PATCH 031/104] Changed `start_attach` function name in debase. Added wrapper function `debase_rb_eval` in debase --- lib/ruby-debug-ide/attach/gdb.rb | 2 +- lib/ruby-debug-ide/attach/lldb.rb | 4 ++-- lib/ruby-debug-ide/attach/native_debugger.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ruby-debug-ide/attach/gdb.rb b/lib/ruby-debug-ide/attach/gdb.rb index 1214816..bf9085c 100644 --- a/lib/ruby-debug-ide/attach/gdb.rb +++ b/lib/ruby-debug-ide/attach/gdb.rb @@ -44,7 +44,7 @@ def set_break(str) def call_start_attach super() execute "call dlopen(\"#{@path_to_attach}\", 2)" - execute 'call start_attach()' + execute 'call debase_start_attach()' set_break(@tbreak) end diff --git a/lib/ruby-debug-ide/attach/lldb.rb b/lib/ruby-debug-ide/attach/lldb.rb index 5d301d2..b9bdc31 100644 --- a/lib/ruby-debug-ide/attach/lldb.rb +++ b/lib/ruby-debug-ide/attach/lldb.rb @@ -42,7 +42,7 @@ def set_break(str) def call_start_attach super() execute "expr (void *) dlopen(\"#{@path_to_attach}\", 2)" - execute 'expr (int) start_attach()' + execute 'expr (int) debase_start_attach()' set_break(@tbreak) end @@ -55,7 +55,7 @@ def check_delimiter(line) end def load_debugger - execute "expr (VALUE) #{@eval_string}" + execute "expr (void) #{@eval_string}" end def to_s diff --git a/lib/ruby-debug-ide/attach/native_debugger.rb b/lib/ruby-debug-ide/attach/native_debugger.rb index cda6876..87eebc9 100644 --- a/lib/ruby-debug-ide/attach/native_debugger.rb +++ b/lib/ruby-debug-ide/attach/native_debugger.rb @@ -21,7 +21,7 @@ def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, ar @debugger_loader_path = debugger_loader_path @argv = argv - @eval_string = "rb_eval_string_protect(\"require '#{@debugger_loader_path}'; load_debugger(#{@gems_to_include.gsub("\"", "'")}, #{@argv.gsub("\"", "'")})\", (int *)0)" + @eval_string = "debase_rb_eval(\"require '#{@debugger_loader_path}'; load_debugger(#{@gems_to_include.gsub("\"", "'")}, #{@argv.gsub("\"", "'")})\", (int *)0)" launch_string = "#{self} #{executable} #{flags}" @pipe = IO.popen(launch_string, 'r+') From e22c174962e9c533cf2c0554c33504781189ddfd Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Mon, 31 Oct 2016 15:01:04 +0300 Subject: [PATCH 032/104] fix argument count for debase_rb_eval --- lib/ruby-debug-ide/attach/native_debugger.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/attach/native_debugger.rb b/lib/ruby-debug-ide/attach/native_debugger.rb index 87eebc9..e0a8c1c 100644 --- a/lib/ruby-debug-ide/attach/native_debugger.rb +++ b/lib/ruby-debug-ide/attach/native_debugger.rb @@ -21,7 +21,7 @@ def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, ar @debugger_loader_path = debugger_loader_path @argv = argv - @eval_string = "debase_rb_eval(\"require '#{@debugger_loader_path}'; load_debugger(#{@gems_to_include.gsub("\"", "'")}, #{@argv.gsub("\"", "'")})\", (int *)0)" + @eval_string = "debase_rb_eval(\"require '#{@debugger_loader_path}'; load_debugger(#{@gems_to_include.gsub("\"", "'")}, #{@argv.gsub("\"", "'")})\")" launch_string = "#{self} #{executable} #{flags}" @pipe = IO.popen(launch_string, 'r+') From 2dc448021ab62cc47beaab34bf648224f698db73 Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Mon, 31 Oct 2016 15:01:22 +0300 Subject: [PATCH 033/104] bump --- lib/ruby-debug-ide/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index 48c578c..8b224d7 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1.beta2' + IDE_VERSION='0.6.1.beta3' end From 1e091ce77eecebe9ee92ef4490beffb9b708dbc3 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Wed, 9 Nov 2016 13:44:59 +0300 Subject: [PATCH 034/104] We should use stdout to print greeting message in attach mode because gksudo swallows stderr. --- bin/gdb_wrapper | 2 +- lib/ruby-debug-ide.rb | 2 +- lib/ruby-debug-ide/greeter.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index 905e741..56eb0d8 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -72,7 +72,7 @@ options.gems_to_include.each do |gem_path| end require 'ruby-debug-ide/greeter' -Debugger::print_greeting_msg(nil, nil) +Debugger::print_greeting_msg($stdout, nil, nil) require 'ruby-debug-ide/attach/util' require 'ruby-debug-ide/attach/native_debugger' diff --git a/lib/ruby-debug-ide.rb b/lib/ruby-debug-ide.rb index 30fc079..d100fe5 100644 --- a/lib/ruby-debug-ide.rb +++ b/lib/ruby-debug-ide.rb @@ -111,7 +111,7 @@ def start_control(host, port, notify_dispatcher) # "localhost" and nil have problems on some systems. host ||= '127.0.0.1' server = TCPServer.new(host, port) - print_greeting_msg(host, port) + print_greeting_msg($stderr, host, port) notify_dispatcher(port) if notify_dispatcher while (session = server.accept) $stderr.puts "Connected from #{session.peeraddr[2]}" if Debugger.cli_debug diff --git a/lib/ruby-debug-ide/greeter.rb b/lib/ruby-debug-ide/greeter.rb index a851913..df9b042 100644 --- a/lib/ruby-debug-ide/greeter.rb +++ b/lib/ruby-debug-ide/greeter.rb @@ -10,7 +10,7 @@ module Debugger class << self - def print_greeting_msg(host, port) + def print_greeting_msg(stream, host, port) base_gem_name = if defined?(JRUBY_VERSION) || RUBY_VERSION < '1.9.0' 'ruby-debug-base' elsif RUBY_VERSION < '2.0.0' @@ -33,7 +33,7 @@ def print_greeting_msg(host, port) msg = "Fast Debugger (ruby-debug-ide #{IDE_VERSION}, #{base_gem_name} #{VERSION}, file filtering is #{file_filtering_support})" + listens_on - $stderr.printf msg + stream.printf msg end end From 7e69ab7ad8941b899a6605e31dfc32ccc333477f Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Wed, 9 Nov 2016 15:31:15 +0300 Subject: [PATCH 035/104] refactoring -- moved trap initialization to main --- bin/gdb_wrapper | 11 +++++++++++ lib/ruby-debug-ide/attach/util.rb | 10 ---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index 56eb0d8..0f0709d 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -79,6 +79,17 @@ require 'ruby-debug-ide/attach/native_debugger' require 'ruby-debug-ide/attach/process_thread' debugger = choose_debugger(options.ruby_path, options.pid, options.gems_to_include, debugger_loader_path, argv) + +trap('INT') do + unless debugger.pipe.closed? + $stderr.puts "backtraces for threads:\n\n" + debugger.process_threads.each do |thread| + $stderr.puts "#{thread.thread_info}\n#{thread.last_bt}\n\n" + end + end + exit! +end + debugger.attach_to_process debugger.set_flags diff --git a/lib/ruby-debug-ide/attach/util.rb b/lib/ruby-debug-ide/attach/util.rb index 035933c..faa8478 100644 --- a/lib/ruby-debug-ide/attach/util.rb +++ b/lib/ruby-debug-ide/attach/util.rb @@ -21,15 +21,5 @@ def choose_debugger(ruby_path, pid, gems_to_include, debugger_loader_path, argv) raise 'Neither gdb nor lldb was found. Aborting.' end - trap('INT') do - unless debugger.pipe.closed? - $stderr.puts "backtraces for threads:\n\n" - debugger.process_threads.each do |thread| - $stderr.puts "#{thread.thread_info}\n#{thread.last_bt}\n\n" - end - end - exit! - end - debugger end From e1d1d363ae4c2338726a1acaa3aafe1abaadbe82 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Wed, 9 Nov 2016 15:40:01 +0300 Subject: [PATCH 036/104] better handling of int signal. --- bin/gdb_wrapper | 10 +++++++--- lib/ruby-debug-ide/attach/native_debugger.rb | 5 ++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index 0f0709d..c89bd8d 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -81,11 +81,15 @@ require 'ruby-debug-ide/attach/process_thread' debugger = choose_debugger(options.ruby_path, options.pid, options.gems_to_include, debugger_loader_path, argv) trap('INT') do - unless debugger.pipe.closed? + unless debugger.exited? $stderr.puts "backtraces for threads:\n\n" - debugger.process_threads.each do |thread| - $stderr.puts "#{thread.thread_info}\n#{thread.last_bt}\n\n" + process_threads = debugger.process_threads + if process_threads + process_threads.each do |thread| + $stderr.puts "#{thread.thread_info}\n#{thread.last_bt}\n\n" + end end + debugger.exit end exit! end diff --git a/lib/ruby-debug-ide/attach/native_debugger.rb b/lib/ruby-debug-ide/attach/native_debugger.rb index e0a8c1c..05ea9d9 100644 --- a/lib/ruby-debug-ide/attach/native_debugger.rb +++ b/lib/ruby-debug-ide/attach/native_debugger.rb @@ -118,8 +118,11 @@ def load_debugger end + def exited? + @pipe.closed? + end + def exit - execute 'q' @pipe.close end From 279e28ec8e0d0be60d64978a27ab9af686242ee1 Mon Sep 17 00:00:00 2001 From: equivalence1 Date: Thu, 15 Dec 2016 03:45:48 +0300 Subject: [PATCH 037/104] exec monkey patch fix --- bin/rdebug-ide | 3 +- lib/ruby-debug-ide/commands/control.rb | 2 +- lib/ruby-debug-ide/multiprocess.rb | 22 ++++++++++++-- lib/ruby-debug-ide/multiprocess/pre_child.rb | 2 -- lib/ruby-debug-ide/multiprocess/unmonkey.rb | 31 ++++++++++++++++++++ 5 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 lib/ruby-debug-ide/multiprocess/unmonkey.rb diff --git a/bin/rdebug-ide b/bin/rdebug-ide index dced3a7..69b2c60 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -117,6 +117,7 @@ if options.dispatcher_port != -1 else require_relative '../lib/ruby-debug-ide/multiprocess' end + Debugger::MultiProcess.do_monkey ENV['DEBUGGER_STORED_RUBYLIB'] = ENV['RUBYLIB'] old_opts = ENV['RUBYOPT'] || '' @@ -140,8 +141,6 @@ Debugger.evaluation_timeout = options.evaluation_timeout Debugger.catchpoint_deleted_event = options.catchpoint_deleted_event || options.rm_protocol_extensions Debugger.value_as_nested_element = options.value_as_nested_element || options.rm_protocol_extensions -Debugger.attached = true - if options.attach_mode if Debugger::FRONT_END == "debase" Debugger.init_variables diff --git a/lib/ruby-debug-ide/commands/control.rb b/lib/ruby-debug-ide/commands/control.rb index bc09bda..6fd030f 100644 --- a/lib/ruby-debug-ide/commands/control.rb +++ b/lib/ruby-debug-ide/commands/control.rb @@ -136,9 +136,9 @@ def regexp end def execute - Debugger.attached = false Debugger.stop Debugger.interface.close + Debugger::MultiProcess.undo_monkey Debugger.control_thread = nil Thread.current.exit #@control_thread is a current thread end diff --git a/lib/ruby-debug-ide/multiprocess.rb b/lib/ruby-debug-ide/multiprocess.rb index 1366327..4342ef7 100644 --- a/lib/ruby-debug-ide/multiprocess.rb +++ b/lib/ruby-debug-ide/multiprocess.rb @@ -1,7 +1,23 @@ -if RUBY_VERSION < "1.9" +if RUBY_VERSION < '1.9' require 'ruby-debug-ide/multiprocess/pre_child' - require 'ruby-debug-ide/multiprocess/monkey' else require_relative 'multiprocess/pre_child' - require_relative 'multiprocess/monkey' +end + +module Debugger + module MultiProcess + class << self + def do_monkey + load File.expand_path(File.dirname(__FILE__) + '/multiprocess/monkey.rb') + end + + def undo_monkey + if ENV['IDE_PROCESS_DISPATCHER'] + load File.expand_path(File.dirname(__FILE__) + '/multiprocess/unmonkey.rb') + ruby_opts = ENV['RUBYOPT'].split(' ') + ENV['RUBYOPT'] = ruby_opts.keep_if {|opt| !opt.end_with?('ruby-debug-ide/multiprocess/starter')}.join(' ') + end + end + end + end end \ No newline at end of file diff --git a/lib/ruby-debug-ide/multiprocess/pre_child.rb b/lib/ruby-debug-ide/multiprocess/pre_child.rb index 7a2f4fc..eb96242 100644 --- a/lib/ruby-debug-ide/multiprocess/pre_child.rb +++ b/lib/ruby-debug-ide/multiprocess/pre_child.rb @@ -2,8 +2,6 @@ module Debugger module MultiProcess class << self def pre_child(options = nil) - return unless Debugger.attached - require 'socket' require 'ostruct' diff --git a/lib/ruby-debug-ide/multiprocess/unmonkey.rb b/lib/ruby-debug-ide/multiprocess/unmonkey.rb new file mode 100644 index 0000000..82e6c6e --- /dev/null +++ b/lib/ruby-debug-ide/multiprocess/unmonkey.rb @@ -0,0 +1,31 @@ +module Debugger + module MultiProcess + def self.restore_fork + %Q{ + alias fork pre_debugger_fork + } + end + + def self.restore_exec + %Q{ + alias exec pre_debugger_exec + } + end + end +end + +module Kernel + class << self + module_eval Debugger::MultiProcess.restore_fork + module_eval Debugger::MultiProcess.restore_exec + end + module_eval Debugger::MultiProcess.restore_fork + module_eval Debugger::MultiProcess.restore_exec +end + +module Process + class << self + module_eval Debugger::MultiProcess.restore_fork + module_eval Debugger::MultiProcess.restore_exec + end +end \ No newline at end of file From b0fddb600a50311dca452c10097aeadc174b93b3 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Thu, 22 Dec 2016 14:18:46 +0300 Subject: [PATCH 038/104] bump gem version --- lib/ruby-debug-ide/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index 8b224d7..bda7cbb 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1.beta3' + IDE_VERSION='0.6.1.beta4' end From 1fc391126a64239995a73f3e91b17cbbac12190e Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Fri, 9 Jun 2017 18:22:42 +0300 Subject: [PATCH 039/104] uncoding for >2.0 fixed --- lib/ruby-debug-ide/command.rb | 1 + lib/ruby-debug-ide/version.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/command.rb b/lib/ruby-debug-ide/command.rb index 1839af2..4d4f732 100644 --- a/lib/ruby-debug-ide/command.rb +++ b/lib/ruby-debug-ide/command.rb @@ -118,6 +118,7 @@ def timeout(sec) def debug_eval(str, b = get_binding) begin str = str.to_s + str.force_encoding('UTF-8') if(RUBY_VERSION > 2.0) to_inspect = Command.unescape_incoming(str) max_time = Debugger.evaluation_timeout @printer.print_debug("Evaluating %s with timeout after %i sec", str, max_time) diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index bda7cbb..560323f 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1.beta4' + IDE_VERSION='0.6.1.beta5' end From e5f34cde272ec156c7f2c0d283778c1656b9c89f Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Wed, 5 Jul 2017 11:36:17 +0300 Subject: [PATCH 040/104] zeus deadlock fix --- lib/ruby-debug-ide/multiprocess/starter.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ruby-debug-ide/multiprocess/starter.rb b/lib/ruby-debug-ide/multiprocess/starter.rb index f8435e4..5e312f8 100644 --- a/lib/ruby-debug-ide/multiprocess/starter.rb +++ b/lib/ruby-debug-ide/multiprocess/starter.rb @@ -6,5 +6,6 @@ end require 'ruby-debug-ide' require 'ruby-debug-ide/multiprocess' + Debugger::MultiProcess::do_monkey Debugger::MultiProcess::pre_child end \ No newline at end of file From 50b61a4d414cae63841aa6daaa77fc63fa583396 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Wed, 5 Jul 2017 11:46:19 +0300 Subject: [PATCH 041/104] cleanup --- lib/ruby-debug-ide/command.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/command.rb b/lib/ruby-debug-ide/command.rb index 4d4f732..5f099be 100644 --- a/lib/ruby-debug-ide/command.rb +++ b/lib/ruby-debug-ide/command.rb @@ -118,7 +118,7 @@ def timeout(sec) def debug_eval(str, b = get_binding) begin str = str.to_s - str.force_encoding('UTF-8') if(RUBY_VERSION > 2.0) + str.force_encoding('UTF-8') if(RUBY_VERSION > '2.0') to_inspect = Command.unescape_incoming(str) max_time = Debugger.evaluation_timeout @printer.print_debug("Evaluating %s with timeout after %i sec", str, max_time) From 03a6101dfba0e36e78729aee86aba94077df2902 Mon Sep 17 00:00:00 2001 From: ViugiNick Date: Tue, 25 Jul 2017 15:22:43 +0300 Subject: [PATCH 042/104] inspect memory limit fix (#100) Add time and memory limit checks for `inspect` and `to_s` Some automatic UI-related queries to the debugger may cause OOM or hangs in the case of recursive or just very large data structures being inspected. This commit adds the possibility (enabled by default) to add time and memory limits for dangerous calls to objects from a user codebase. Related: RUBY-18275, RUBY-18121, RUBY-19520. Commits reviewed and squashed: * inspect memory limit * cleanup * Option to disable memory check is added * disable memory limit in the same option * cleanup: formating fix, rename variable, rewrite on "compact = if (...)" * fixed exeption message * memory limit logging added * debug output deleted * print_msg -> print_debug * object_id deleted from the exeption message * Fixes according to the review * timelimit for inspect evaluation added * The handler is called with a probability of 25% * cleanup * inspect_with_alloc_control -> exec_with_... and heuristic break for variable inspect * fixes according to reiew --- bin/rdebug-ide | 11 +++++ lib/ruby-debug-ide/xml_printer.rb | 81 +++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/bin/rdebug-ide b/bin/rdebug-ide index 69b2c60..34d563d 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -36,6 +36,17 @@ Usage: rdebug-ide is supposed to be called from RDT, NetBeans, RubyMine, or EOB opts.separator "" opts.separator "Options:" + + ENV['DEBUGGER_MEMORY_LIMIT'] = '10' + opts.on("-m", "--memory-limit LIMIT", Integer, "evaluation memory limit in mb (default: 10)") do |limit| + ENV['DEBUGGER_MEMORY_LIMIT'] = limit + end + + ENV['INSPECT_TIME_LIMIT'] = '100' + opts.on("-t", "--time-limit LIMIT", Integer, "evaluation time limit in milliseconds (default: 100)") do |limit| + ENV['INSPECT_TIME_LIMIT'] = limit + end + opts.on("-h", "--host HOST", "Host name used for remote debugging") {|host| options.host = host} opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port} opts.on("--dispatcher-port PORT", Integer, "Port used for multi-process debugging dispatcher") do |dp| diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 00f08b5..0b0e607 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -1,9 +1,30 @@ require 'stringio' require 'cgi' require 'monitor' +require 'objspace' module Debugger + class MemoryLimitError < StandardError + attr_reader :message + attr_reader :backtrace + + def initialize(message, backtrace = '') + @message = message + @backtrace = backtrace + end + end + + class TimeLimitError < StandardError + attr_reader :message + attr_reader :backtrace + + def initialize(message, backtrace = '') + @message = message + @backtrace = backtrace + end + end + class XmlPrinter # :nodoc: class ExceptionProxy instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$|^instance_variables$|^instance_eval$)/ } @@ -137,7 +158,45 @@ def print_string(string) print_variable('encoding', string.encoding, 'instance') if string.respond_to?('encoding') end end - + + def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, return_message_if_overflow) + curr_thread = Thread.current + result = nil + inspect_thread = DebugThread.start { + start_alloc_size = ObjectSpace.memsize_of_all + start_time = Time.now.to_f + + trace = TracePoint.new(:c_call, :call) do |tp| + + if(rand > 0.75) + curr_alloc_size = ObjectSpace.memsize_of_all + curr_time = Time.now.to_f + + if((curr_time - start_time) * 1e3 > time_limit) + curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", "#{caller.map{|l| "\t#{l}"}.join("\n")}") + inspect_thread.kill + end + + start_alloc_size = curr_alloc_size if (curr_alloc_size < start_alloc_size) + + if(curr_alloc_size - start_alloc_size > 1e6 * memory_limit) + curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", "#{caller.map{|l| "\t#{l}"}.join("\n")}") + inspect_thread.kill + end + end + end.enable { + result = value.send exec_method + } + } + inspect_thread.join + inspect_thread.kill + return result + rescue MemoryLimitError, TimeLimitError => e + print_debug(e.message + "\n" + e.backtrace) + + return return_message_if_overflow ? e.message : nil + end + def print_variable(name, value, kind) name = name.to_s if value.nil? @@ -157,7 +216,13 @@ def print_variable(name, value, kind) value_str = value else has_children = !value.instance_variables.empty? || !value.class.class_variables.empty? - value_str = value.to_s || 'nil' rescue "<#to_s method raised exception: #{$!}>" + + value_str = if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) + value.to_s || 'nil' rescue "<#to_s method raised exception: #{$!}>" + else + exec_with_allocation_control(value, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, false) || 'nil' rescue "<#to_s method raised exception: #{$!}>" + end + unless value_str.is_a?(String) value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String." end @@ -382,11 +447,17 @@ def max_compact_name_size def compact_array_str(value) slice = value[0..10] - compact = slice.inspect - if value.size != slice.size + + compact = if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) + slice.inspect + else + exec_with_allocation_control(slice, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :inspect, true) + end + + if compact && value.size != slice.size compact[0..compact.size-2] + ", ...]" end - compact + compact end def compact_hash_str(value) From da5c61935fad57810ebabb77f8c1619c70f5ca7b Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Tue, 1 Aug 2017 16:06:47 +0300 Subject: [PATCH 043/104] set tl and ml for calculating self.to_s --- lib/ruby-debug-ide/commands/variables.rb | 3 ++- lib/ruby-debug-ide/xml_printer.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ruby-debug-ide/commands/variables.rb b/lib/ruby-debug-ide/commands/variables.rb index cc8d935..ccc5841 100644 --- a/lib/ruby-debug-ide/commands/variables.rb +++ b/lib/ruby-debug-ide/commands/variables.rb @@ -129,7 +129,8 @@ def execute locals = @state.context.frame_locals(@state.frame_pos) _self = @state.context.frame_self(@state.frame_pos) begin - locals['self'] = _self unless "main" == _self.to_s + _self_str = exec_with_allocation_control(_self, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, false) + locals['self'] = _self unless "main" == _self_str rescue => ex locals['self'] = "" $stderr << "Cannot evaluate self\n#{ex.class.name}: #{ex.message}\n #{ex.backtrace.join("\n ")}" diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 0b0e607..d5fd24b 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -220,7 +220,7 @@ def print_variable(name, value, kind) value_str = if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) value.to_s || 'nil' rescue "<#to_s method raised exception: #{$!}>" else - exec_with_allocation_control(value, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, false) || 'nil' rescue "<#to_s method raised exception: #{$!}>" + exec_with_allocation_control(value, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, true) || 'nil' rescue "<#to_s method raised exception: #{$!}>" end unless value_str.is_a?(String) From f821ffbfd6df32c2d34cf48b0b77ca66fc03045e Mon Sep 17 00:00:00 2001 From: viuginick Date: Thu, 3 Aug 2017 23:44:24 +0300 Subject: [PATCH 044/104] refactor xml_printer --- lib/ruby-debug-ide/xml_printer.rb | 178 +++++++++++++++--------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index d5fd24b..0334266 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -5,7 +5,7 @@ module Debugger - class MemoryLimitError < StandardError + class MemoryLimitError < StandardError attr_reader :message attr_reader :backtrace @@ -13,9 +13,9 @@ def initialize(message, backtrace = '') @message = message @backtrace = backtrace end - end + end - class TimeLimitError < StandardError + class TimeLimitError < StandardError attr_reader :message attr_reader :backtrace @@ -23,11 +23,11 @@ def initialize(message, backtrace = '') @message = message @backtrace = backtrace end - end + end class XmlPrinter # :nodoc: class ExceptionProxy - instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$|^instance_variables$|^instance_eval$)/ } + instance_methods.each {|m| undef_method m unless m =~ /(^__|^send$|^object_id$|^instance_variables$|^instance_eval$)/} def initialize(exception) @exception = exception @@ -35,9 +35,9 @@ def initialize(exception) @backtrace = Debugger.cleanup_backtrace(exception.backtrace) end - private - def method_missing(called, *args, &block) - @exception.__send__(called, *args, &block) + private + def method_missing(called, *args, &block) + @exception.__send__(called, *args, &block) end end @@ -52,15 +52,15 @@ def #{mname}(*args, &block) end end } - end + end @@monitor = Monitor.new attr_accessor :interface - + def initialize(interface) @interface = interface end - + def print_msg(*args) msg, *args = args xml_message = CGI.escapeHTML(msg % args) @@ -83,7 +83,7 @@ def print_error(*args) print CGI.escapeHTML(msg % args) end end - + def print_frames(context, current_frame_id) print_element("frames") do (0...context.stack_size).each do |id| @@ -91,18 +91,18 @@ def print_frames(context, current_frame_id) end end end - + def print_current_frame(frame_pos) print_debug "Selected frame no #{frame_pos}" end - + def print_frame(context, frame_id, current_frame_id) # idx + 1: one-based numbering as classic-debugger file = context.frame_file(frame_id) print "", - frame_id + 1, CGI.escapeHTML(File.expand_path(file)), context.frame_line(frame_id) + frame_id + 1, CGI.escapeHTML(File.expand_path(file)), context.frame_line(frame_id) end - + def print_contexts(contexts) print_element("threads") do contexts.each do |c| @@ -110,11 +110,11 @@ def print_contexts(contexts) end end end - + def print_context(context) print "", context.thnum, context.thread.status, Process.pid end - + def print_variables(vars, kind) print_element("variables") do # print self at top position @@ -124,26 +124,26 @@ def print_variables(vars, kind) end end end - + def print_array(array) print_element("variables") do - index = 0 - array.each { |e| - print_variable('[' + index.to_s + ']', e, 'instance') - index += 1 + index = 0 + array.each {|e| + print_variable('[' + index.to_s + ']', e, 'instance') + index += 1 } end end - + def print_hash(hash) print_element("variables") do - hash.keys.each { | k | + hash.keys.each {|k| if k.class.name == "String" name = '\'' + k + '\'' else name = k.to_s end - print_variable(name, hash[k], 'instance') + print_variable(name, hash[k], 'instance') } end end @@ -155,7 +155,7 @@ def print_string(string) InspectCommand.reference_result(bytes) print_variable('bytes', bytes, 'instance') end - print_variable('encoding', string.encoding, 'instance') if string.respond_to?('encoding') + print_variable('encoding', string.encoding, 'instance') if string.respond_to?('encoding') end end @@ -165,22 +165,22 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, r inspect_thread = DebugThread.start { start_alloc_size = ObjectSpace.memsize_of_all start_time = Time.now.to_f - + trace = TracePoint.new(:c_call, :call) do |tp| - - if(rand > 0.75) + + if (rand > 0.75) curr_alloc_size = ObjectSpace.memsize_of_all curr_time = Time.now.to_f - - if((curr_time - start_time) * 1e3 > time_limit) - curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", "#{caller.map{|l| "\t#{l}"}.join("\n")}") + + if ((curr_time - start_time) * 1e3 > time_limit) + curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", "#{caller.map {|l| "\t#{l}"}.join("\n")}") inspect_thread.kill end start_alloc_size = curr_alloc_size if (curr_alloc_size < start_alloc_size) - - if(curr_alloc_size - start_alloc_size > 1e6 * memory_limit) - curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", "#{caller.map{|l| "\t#{l}"}.join("\n")}") + + if (curr_alloc_size - start_alloc_size > 1e6 * memory_limit) + curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", "#{caller.map {|l| "\t#{l}"}.join("\n")}") inspect_thread.kill end end @@ -193,7 +193,7 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, r return result rescue MemoryLimitError, TimeLimitError => e print_debug(e.message + "\n" + e.backtrace) - + return return_message_if_overflow ? e.message : nil end @@ -206,7 +206,7 @@ def print_variable(name, value, kind) if value.is_a?(Array) || value.is_a?(Hash) has_children = !value.empty? if has_children - size = value.size + size = value.size value_str = "#{value.class} (#{value.size} element#{size > 1 ? "s" : "" })" else value_str = "Empty #{value.class}" @@ -214,33 +214,33 @@ def print_variable(name, value, kind) elsif value.is_a?(String) has_children = value.respond_to?('bytes') || value.respond_to?('encoding') value_str = value - else + else has_children = !value.instance_variables.empty? || !value.class.class_variables.empty? - + value_str = if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) - value.to_s || 'nil' rescue "<#to_s method raised exception: #{$!}>" - else - exec_with_allocation_control(value, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, true) || 'nil' rescue "<#to_s method raised exception: #{$!}>" - end - + value.to_s || 'nil' rescue "<#to_s method raised exception: #{$!}>" + else + exec_with_allocation_control(value, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, true) || 'nil' rescue "<#to_s method raised exception: #{$!}>" + end + unless value_str.is_a?(String) - value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String." + value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String." end end if value_str.respond_to?('encode') # noinspection RubyEmptyRescueBlockInspection begin - value_str = value_str.encode("UTF-8") + value_str = value_str.encode("UTF-8") rescue end end value_str = handle_binary_data(value_str) escaped_value_str = CGI.escapeHTML(value_str) print("", - CGI.escapeHTML(name), build_compact_value_attr(value, value_str), kind, - build_value_attr(escaped_value_str), value.class, - has_children, value.respond_to?(:object_id) ? value.object_id : value.id) + CGI.escapeHTML(name), build_compact_value_attr(value, value_str), kind, + build_value_attr(escaped_value_str), value.class, + has_children, value.respond_to?(:object_id) ? value.object_id : value.id) print("", escaped_value_str) if Debugger.value_as_nested_element print('') rescue StandardError => e @@ -263,28 +263,28 @@ def print_file_filter_status(status) def print_breakpoints(breakpoints) print_element 'breakpoints' do - breakpoints.sort_by{|b| b.id }.each do |b| + breakpoints.sort_by {|b| b.id}.each do |b| print "", b.id, CGI.escapeHTML(b.source), b.pos.to_s end end end - + def print_breakpoint_added(b) print "", b.id, CGI.escapeHTML(b.source), b.pos end - + def print_breakpoint_deleted(b) print "", b.id end - + def print_breakpoint_enabled(b) print "", b.id end - + def print_breakpoint_disabled(b) print "", b.id end - + def print_contdition_set(bp_id) print "", bp_id end @@ -308,24 +308,24 @@ def print_expressions(exps) end end unless exps.empty? end - + def print_expression(exp, value, idx) print "", exp, value, idx end def print_expression_info(incomplete, prompt, indent) print "", - incomplete, CGI.escapeHTML(prompt), indent + incomplete, CGI.escapeHTML(prompt), indent end - + def print_eval(exp, value) - print "", CGI.escapeHTML(exp), value + print "", CGI.escapeHTML(exp), value end - + def print_pp(value) print value end - + def print_list(b, e, file, line) print "[%d, %d] in %s\n", b, e, file if (lines = Debugger.source_for(file)) @@ -342,7 +342,7 @@ def print_list(b, e, file, line) print "No source-file available for %s\n", file end end - + def print_methods(methods) print_element "methods" do methods.each do |method| @@ -350,31 +350,31 @@ def print_methods(methods) end end end - + # Events - + def print_breakpoint(_, breakpoint) print("", - CGI.escapeHTML(breakpoint.source), breakpoint.pos, Debugger.current_context.thnum) + CGI.escapeHTML(breakpoint.source), breakpoint.pos, Debugger.current_context.thnum) end - + def print_catchpoint(exception) context = Debugger.current_context - print("", - CGI.escapeHTML(context.frame_file(0)), context.frame_line(0), exception.class, CGI.escapeHTML(exception.to_s), context.thnum) + print("", + CGI.escapeHTML(context.frame_file(0)), context.frame_line(0), exception.class, CGI.escapeHTML(exception.to_s), context.thnum) end - + def print_trace(context, file, line) Debugger::print_debug "trace: location=\"%s:%s\", threadId=%d", file, line, context.thnum # TBD: do we want to clog fronend with the elements? There are tons of them. # print "", file, line, context.thnum end - + def print_at_line(context, file, line) print "", CGI.escapeHTML(File.expand_path(file)), line, context.thnum, context.stack_size end - + def print_exception(exception, _) print_element("variables") do proxy = ExceptionProxy.new(exception) @@ -382,21 +382,21 @@ def print_exception(exception, _) print_variable('error', proxy, 'exception') end rescue Exception - print "", - exception.class, CGI.escapeHTML(exception.to_s) + print "", + exception.class, CGI.escapeHTML(exception.to_s) end - + def print_inspect(eval_result) - print_element("variables") do + print_element("variables") do print_variable("eval_result", eval_result, 'local') end end - + def print_load_result(file, exception=nil) if exception - print("", file, exception.class, CGI.escapeHTML(exception.to_s)) + print("", file, exception.class, CGI.escapeHTML(exception.to_s)) else - print("", file) + print("", file) end end @@ -410,7 +410,7 @@ def print_element(name) end private - + def print(*params) Debugger::print_debug(*params) @interface.print(*params) @@ -446,28 +446,28 @@ def max_compact_name_size end def compact_array_str(value) - slice = value[0..10] + slice = value[0..10] compact = if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) slice.inspect - else + else exec_with_allocation_control(slice, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :inspect, true) - end - + end + if compact && value.size != slice.size compact[0..compact.size-2] + ", ...]" end - compact + compact end def compact_hash_str(value) - slice = value.sort_by { |k, _| k.to_s }[0..5] - compact = slice.map { |kv| "#{kv[0]}: #{handle_binary_data(kv[1])}" }.join(", ") + slice = value.sort_by {|k, _| k.to_s}[0..5] + compact = slice.map {|kv| "#{kv[0]}: #{handle_binary_data(kv[1])}"}.join(", ") "{" + compact + (slice.size != value.size ? ", ..." : "") + "}" end def build_compact_value_attr(value, value_str) - compact_value_str = build_compact_name(value, value_str) + compact_value_str = build_compact_name(value, value_str) compact_value_str.nil? ? '' : "compactValue=\"#{CGI.escapeHTML(compact_value_str)}\"" end @@ -493,7 +493,7 @@ def build_value_attr(escaped_value_str) protect m end end - + end end From 7e5f7f512150efcd59f54d66126bc529f022ef7d Mon Sep 17 00:00:00 2001 From: viuginick Date: Thu, 3 Aug 2017 23:47:52 +0300 Subject: [PATCH 045/104] refactoring: move jruby check into exec_with_allocation_method --- lib/ruby-debug-ide/xml_printer.rb | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 0334266..de93202 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -160,6 +160,9 @@ def print_string(string) end def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, return_message_if_overflow) + if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) + return value.send exec_method + end curr_thread = Thread.current result = nil inspect_thread = DebugThread.start { @@ -217,11 +220,7 @@ def print_variable(name, value, kind) else has_children = !value.instance_variables.empty? || !value.class.class_variables.empty? - value_str = if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) - value.to_s || 'nil' rescue "<#to_s method raised exception: #{$!}>" - else - exec_with_allocation_control(value, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, true) || 'nil' rescue "<#to_s method raised exception: #{$!}>" - end + value_str = exec_with_allocation_control(value, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, true) || 'nil' rescue "<#to_s method raised exception: #{$!}>" unless value_str.is_a?(String) value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String." @@ -448,11 +447,7 @@ def max_compact_name_size def compact_array_str(value) slice = value[0..10] - compact = if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) - slice.inspect - else - exec_with_allocation_control(slice, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :inspect, true) - end + compact = exec_with_allocation_control(slice, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :inspect, true) if compact && value.size != slice.size compact[0..compact.size-2] + ", ...]" From f100c428f21a5207d06e893c70e57a812f18266e Mon Sep 17 00:00:00 2001 From: viuginick Date: Fri, 4 Aug 2017 00:14:15 +0300 Subject: [PATCH 046/104] refactoring: exception message type set by a lambda --- lib/ruby-debug-ide/xml_printer.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index de93202..7fdd803 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -5,6 +5,12 @@ module Debugger + module OverflowMessageType + NIL_MESSAGE = lambda {|e| nil} + EXCEPTION_MESSAGE = lambda {|e| e.message} + SPECIAL_SYMBOL_MESSAGE = lambda {|e| ''} + end + class MemoryLimitError < StandardError attr_reader :message attr_reader :backtrace @@ -159,7 +165,7 @@ def print_string(string) end end - def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, return_message_if_overflow) + def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) return value.send exec_method end @@ -197,7 +203,7 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, r rescue MemoryLimitError, TimeLimitError => e print_debug(e.message + "\n" + e.backtrace) - return return_message_if_overflow ? e.message : nil + return overflow_message_type.call(e) end def print_variable(name, value, kind) @@ -220,7 +226,7 @@ def print_variable(name, value, kind) else has_children = !value.instance_variables.empty? || !value.class.class_variables.empty? - value_str = exec_with_allocation_control(value, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, true) || 'nil' rescue "<#to_s method raised exception: #{$!}>" + value_str = exec_with_allocation_control(value, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) || 'nil' rescue "<#to_s method raised exception: #{$!}>" unless value_str.is_a?(String) value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String." @@ -447,7 +453,7 @@ def max_compact_name_size def compact_array_str(value) slice = value[0..10] - compact = exec_with_allocation_control(slice, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :inspect, true) + compact = exec_with_allocation_control(slice, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :inspect, OverflowMessageType::NIL_MESSAGE) if compact && value.size != slice.size compact[0..compact.size-2] + ", ...]" From da413e437ad46e3422675b0462ec00237bc6a941 Mon Sep 17 00:00:00 2001 From: viuginick Date: Fri, 4 Aug 2017 00:16:12 +0300 Subject: [PATCH 047/104] hix hashes with long keys/values --- lib/ruby-debug-ide/xml_printer.rb | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 7fdd803..6130901 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -147,7 +147,7 @@ def print_hash(hash) if k.class.name == "String" name = '\'' + k + '\'' else - name = k.to_s + name = exec_with_allocation_control(k, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) end print_variable(name, hash[k], 'instance') } @@ -462,8 +462,19 @@ def compact_array_str(value) end def compact_hash_str(value) - slice = value.sort_by {|k, _| k.to_s}[0..5] - compact = slice.map {|kv| "#{kv[0]}: #{handle_binary_data(kv[1])}"}.join(", ") + keys_strings = Hash.new + + slice = value.sort_by do |k, _| + keys_string = exec_with_allocation_control(k, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE) + keys_strings[k] = keys_string + keys_string + end[0..5] + + compact = slice.map do |kv| + key_string = keys_strings[kv[0]] + value_string = exec_with_allocation_control(kv[1], ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE) + "#{key_string}: #{handle_binary_data(value_string)}" + end.join(", ") "{" + compact + (slice.size != value.size ? ", ..." : "") + "}" end From 828f0d8685ff1082d4abe6f1eccb270028a4c334 Mon Sep 17 00:00:00 2001 From: viuginick Date: Fri, 4 Aug 2017 00:17:12 +0300 Subject: [PATCH 048/104] let timeout checks for jruby and little refactoring --- lib/ruby-debug-ide/xml_printer.rb | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 6130901..5e449d1 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -166,19 +166,20 @@ def print_string(string) end def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) + check_memory_limit = true if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) - return value.send exec_method + check_memory_limit = false end curr_thread = Thread.current result = nil inspect_thread = DebugThread.start { - start_alloc_size = ObjectSpace.memsize_of_all + + start_alloc_size = ObjectSpace.memsize_of_all if (check_memory_limit) start_time = Time.now.to_f trace = TracePoint.new(:c_call, :call) do |tp| if (rand > 0.75) - curr_alloc_size = ObjectSpace.memsize_of_all curr_time = Time.now.to_f if ((curr_time - start_time) * 1e3 > time_limit) @@ -186,11 +187,14 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, o inspect_thread.kill end - start_alloc_size = curr_alloc_size if (curr_alloc_size < start_alloc_size) + if (check_memory_limit) + curr_alloc_size = ObjectSpace.memsize_of_all + start_alloc_size = curr_alloc_size if (curr_alloc_size < start_alloc_size) - if (curr_alloc_size - start_alloc_size > 1e6 * memory_limit) - curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", "#{caller.map {|l| "\t#{l}"}.join("\n")}") - inspect_thread.kill + if (curr_alloc_size - start_alloc_size > 1e6 * memory_limit) + curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", "#{caller.map {|l| "\t#{l}"}.join("\n")}") + inspect_thread.kill + end end end end.enable { @@ -309,7 +313,7 @@ def print_catchpoint_deleted(exception_class_name) def print_expressions(exps) print_element "expressions" do exps.each_with_index do |(exp, value), idx| - print_expression(exp, value, idx+1) + print_expression(exp, value, idx + 1) end end unless exps.empty? end @@ -335,11 +339,11 @@ def print_list(b, e, file, line) print "[%d, %d] in %s\n", b, e, file if (lines = Debugger.source_for(file)) b.upto(e) do |n| - if n > 0 && lines[n-1] + if n > 0 && lines[n - 1] if n == line - print "=> %d %s\n", n, lines[n-1].chomp + print "=> %d %s\n", n, lines[n - 1].chomp else - print " %d %s\n", n, lines[n-1].chomp + print " %d %s\n", n, lines[n - 1].chomp end end end @@ -397,7 +401,7 @@ def print_inspect(eval_result) end end - def print_load_result(file, exception=nil) + def print_load_result(file, exception = nil) if exception print("", file, exception.class, CGI.escapeHTML(exception.to_s)) else @@ -456,7 +460,7 @@ def compact_array_str(value) compact = exec_with_allocation_control(slice, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :inspect, OverflowMessageType::NIL_MESSAGE) if compact && value.size != slice.size - compact[0..compact.size-2] + ", ...]" + compact[0..compact.size - 2] + ", ...]" end compact end From 4333f10af68a30806a7d1b097452487d13b271ec Mon Sep 17 00:00:00 2001 From: ViugiNick Date: Mon, 7 Aug 2017 12:35:11 +0300 Subject: [PATCH 049/104] improve attach mode to affect child processes (#108) This commit enables the attachment to the child processes of the original one. Useful for debugging application servers (see https://youtrack.jetbrains.com/issue/RUBY-19369) --- bin/gdb_wrapper | 48 +++---------- bin/rdebug-ide | 5 +- lib/ruby-debug-ide/attach/util.rb | 71 ++++++++++++++++++++ lib/ruby-debug-ide/multiprocess/pre_child.rb | 6 +- 4 files changed, 88 insertions(+), 42 deletions(-) diff --git a/bin/gdb_wrapper b/bin/gdb_wrapper index c89bd8d..a439204 100755 --- a/bin/gdb_wrapper +++ b/bin/gdb_wrapper @@ -1,6 +1,7 @@ #!/usr/bin/env ruby require 'optparse' +require 'thread' require 'ostruct' $stdout.sync = true @@ -65,6 +66,7 @@ unless options.ruby_path end argv = '["' + ARGV * '", "' + '"]' +child_argv = '["' + ARGV * '", "' + "', '--ignore-port" + '"]' debugger_loader_path = File.expand_path(File.dirname(__FILE__)) + '/../lib/ruby-debug-ide/attach/debugger_loader' options.gems_to_include.each do |gem_path| @@ -78,51 +80,17 @@ require 'ruby-debug-ide/attach/util' require 'ruby-debug-ide/attach/native_debugger' require 'ruby-debug-ide/attach/process_thread' -debugger = choose_debugger(options.ruby_path, options.pid, options.gems_to_include, debugger_loader_path, argv) -trap('INT') do - unless debugger.exited? - $stderr.puts "backtraces for threads:\n\n" - process_threads = debugger.process_threads - if process_threads - process_threads.each do |thread| - $stderr.puts "#{thread.thread_info}\n#{thread.last_bt}\n\n" - end - end - debugger.exit - end - exit! -end +child_pids = get_child_pids(options.pid.to_s) +attach_threads = Array.new +attach_threads << attach_and_return_thread(options, options.pid, debugger_loader_path, argv) -debugger.attach_to_process -debugger.set_flags +attach_threads << child_pids.map {|pid| attach_and_return_thread(options, pid, debugger_loader_path, child_argv)} + +attach_threads.each {|thread| thread.join} if options.uid DebugPrinter.print_debug("changing current uid from #{Process.uid} to #{options.uid}") Process::Sys.setuid(options.uid.to_i) end - -if debugger.check_already_under_debug - $stderr.puts "Process #{debugger.pid} is already under debug" - debugger.exit - exit! -end - -should_check_threads_state = true - -while should_check_threads_state - should_check_threads_state = false - debugger.update_threads.each do |thread| - thread.switch - while thread.need_finish_frame - should_check_threads_state = true - thread.finish - end - end -end - -debugger.wait_line_event -debugger.load_debugger -debugger.exit - sleep diff --git a/bin/rdebug-ide b/bin/rdebug-ide index 34d563d..fc03173 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -48,7 +48,7 @@ EOB end opts.on("-h", "--host HOST", "Host name used for remote debugging") {|host| options.host = host} - opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port} + opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port} opts.on("--dispatcher-port PORT", Integer, "Port used for multi-process debugging dispatcher") do |dp| options.dispatcher_port = dp end @@ -71,6 +71,9 @@ EOB opts.on("--attach-mode", "Tells that rdebug-ide is working in attach mode") do options.attach_mode = true end + opts.on("--ignore-port", "Generate another port") do + options.ignore_port = true + end opts.on("--keep-frame-binding", "Keep frame bindings") {options.frame_bind = true} opts.on("--disable-int-handler", "Disables interrupt signal handler") {options.int_handler = false} opts.on("--rubymine-protocol-extensions", "Enable all RubyMine-specific incompatible protocol extensions") do diff --git a/lib/ruby-debug-ide/attach/util.rb b/lib/ruby-debug-ide/attach/util.rb index faa8478..40860d4 100644 --- a/lib/ruby-debug-ide/attach/util.rb +++ b/lib/ruby-debug-ide/attach/util.rb @@ -1,5 +1,76 @@ require 'ruby-debug-ide/attach/lldb' require 'ruby-debug-ide/attach/gdb' +require 'socket' + +def attach_and_return_thread(options, pid, debugger_loader_path, argv) + Thread.new(argv) do |argv| + + debugger = choose_debugger(options.ruby_path, pid, options.gems_to_include, debugger_loader_path, argv) + + trap('INT') do + unless debugger.exited? + $stderr.puts "backtraces for threads:\n\n" + process_threads = debugger.process_threads + if process_threads + process_threads.each do |thread| + $stderr.puts "#{thread.thread_info}\n#{thread.last_bt}\n\n" + end + end + debugger.exit + end + exit! + end + + debugger.attach_to_process + debugger.set_flags + + if debugger.check_already_under_debug + $stderr.puts "Process #{debugger.pid} is already under debug" + debugger.exit + exit! + end + + should_check_threads_state = true + + while should_check_threads_state + should_check_threads_state = false + debugger.update_threads.each do |thread| + thread.switch + while thread.need_finish_frame + should_check_threads_state = true + thread.finish + end + end + end + + debugger.wait_line_event + debugger.load_debugger + debugger.exit + end +end + +def get_child_pids(pid) + return [] unless command_exists 'pgrep' + + pids = Array.new + + q = Queue.new + q.push(pid) + + while (!q.empty?) do + pid = q.pop + + pipe = IO.popen("pgrep -P #{pid}") + + pipe.readlines.each do |child| + child_pid = child.strip.to_i + q.push(child_pid) + pids << child_pid + end + end + + pids +end def command_exists(command) checking_command = "checking command #{command} for existence\n" diff --git a/lib/ruby-debug-ide/multiprocess/pre_child.rb b/lib/ruby-debug-ide/multiprocess/pre_child.rb index eb96242..fd0d41f 100644 --- a/lib/ruby-debug-ide/multiprocess/pre_child.rb +++ b/lib/ruby-debug-ide/multiprocess/pre_child.rb @@ -19,6 +19,11 @@ def pre_child(options = nil) 'notify_dispatcher' => true ) + if(options.ignore_port) + options.port = find_free_port(options.host) + options.notify_dispatcher = true + end + start_debugger(options) end @@ -39,7 +44,6 @@ def start_debugger(options) Debugger.keep_frame_binding = options.frame_bind Debugger.tracing = options.tracing Debugger.cli_debug = options.cli_debug - Debugger.prepare_debugger(options) end From 0e58fa4357f1a040d5805e463eca65ad0b0c8862 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Mon, 7 Aug 2017 13:43:21 +0300 Subject: [PATCH 050/104] Optimized unnecessary to_s --- lib/ruby-debug-ide/commands/variables.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ruby-debug-ide/commands/variables.rb b/lib/ruby-debug-ide/commands/variables.rb index ccc5841..33c4f8e 100644 --- a/lib/ruby-debug-ide/commands/variables.rb +++ b/lib/ruby-debug-ide/commands/variables.rb @@ -129,8 +129,7 @@ def execute locals = @state.context.frame_locals(@state.frame_pos) _self = @state.context.frame_self(@state.frame_pos) begin - _self_str = exec_with_allocation_control(_self, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, false) - locals['self'] = _self unless "main" == _self_str + locals['self'] = _self unless TOPLEVEL_BINDING.eval('self') == _self rescue => ex locals['self'] = "" $stderr << "Cannot evaluate self\n#{ex.class.name}: #{ex.message}\n #{ex.backtrace.join("\n ")}" From 2f7b3e0808c415d43888e003b5ec77c5795bdceb Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Mon, 7 Aug 2017 14:38:21 +0300 Subject: [PATCH 051/104] bump version --- lib/ruby-debug-ide/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index 560323f..b720ba5 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1.beta5' + IDE_VERSION='0.6.1.beta6' end From 7fda8076920c970f01972d73af223bab2c860347 Mon Sep 17 00:00:00 2001 From: ViugiNick Date: Tue, 8 Aug 2017 12:16:49 +0300 Subject: [PATCH 052/104] jruby and error class fix (#116) * jruby and error class fix * Tabs for backtrace output --- lib/ruby-debug-ide/xml_printer.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 5e449d1..aad80f6 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -1,7 +1,9 @@ require 'stringio' require 'cgi' require 'monitor' -require 'objspace' +if (!defined?(JRUBY_VERSION)) + require 'objspace' +end module Debugger @@ -15,7 +17,7 @@ class MemoryLimitError < StandardError attr_reader :message attr_reader :backtrace - def initialize(message, backtrace = '') + def initialize(message, backtrace = []) @message = message @backtrace = backtrace end @@ -25,7 +27,7 @@ class TimeLimitError < StandardError attr_reader :message attr_reader :backtrace - def initialize(message, backtrace = '') + def initialize(message, backtrace = []) @message = message @backtrace = backtrace end @@ -183,7 +185,7 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, o curr_time = Time.now.to_f if ((curr_time - start_time) * 1e3 > time_limit) - curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", "#{caller.map {|l| "\t#{l}"}.join("\n")}") + curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", caller.to_a) inspect_thread.kill end @@ -192,7 +194,7 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, o start_alloc_size = curr_alloc_size if (curr_alloc_size < start_alloc_size) if (curr_alloc_size - start_alloc_size > 1e6 * memory_limit) - curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", "#{caller.map {|l| "\t#{l}"}.join("\n")}") + curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", caller.to_a) inspect_thread.kill end end @@ -205,8 +207,7 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, o inspect_thread.kill return result rescue MemoryLimitError, TimeLimitError => e - print_debug(e.message + "\n" + e.backtrace) - + print_debug(e.message + "\n" + e.backtrace.map{|l| "\t#{l}"}.join("\n")) return overflow_message_type.call(e) end @@ -231,7 +232,6 @@ def print_variable(name, value, kind) has_children = !value.instance_variables.empty? || !value.class.class_variables.empty? value_str = exec_with_allocation_control(value, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) || 'nil' rescue "<#to_s method raised exception: #{$!}>" - unless value_str.is_a?(String) value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String." end From b8783c0f71bde9336883212b336a97f96ac12af9 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Tue, 8 Aug 2017 12:17:56 +0300 Subject: [PATCH 053/104] bump version --- lib/ruby-debug-ide/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index b720ba5..43e2521 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1.beta6' + IDE_VERSION='0.6.1.beta7' end From c3890c6932aaa5653c9a52d6605ba75486966e97 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Tue, 8 Aug 2017 14:32:17 +0300 Subject: [PATCH 054/104] attach only to ruby child processes --- lib/ruby-debug-ide/attach/util.rb | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/ruby-debug-ide/attach/util.rb b/lib/ruby-debug-ide/attach/util.rb index 40860d4..9ab535e 100644 --- a/lib/ruby-debug-ide/attach/util.rb +++ b/lib/ruby-debug-ide/attach/util.rb @@ -1,6 +1,7 @@ require 'ruby-debug-ide/attach/lldb' require 'ruby-debug-ide/attach/gdb' require 'socket' +require 'set' def attach_and_return_thread(options, pid, debugger_loader_path, argv) Thread.new(argv) do |argv| @@ -57,7 +58,7 @@ def get_child_pids(pid) q = Queue.new q.push(pid) - while (!q.empty?) do + until q.empty? do pid = q.pop pipe = IO.popen("pgrep -P #{pid}") @@ -69,7 +70,25 @@ def get_child_pids(pid) end end - pids + filter_ruby_processes(pids) +end + +def filter_ruby_processes(pids) + pipe = IO.popen(%Q(lsof -c ruby | awk '{print $2 ":" $9}' | grep -E 'bin/ruby([[:digit:]]+\.?)*$')) + + ruby_processes = Set.new + + pipe.readlines.each do |process| + pid = process.split(/:/).first + ruby_processes.add(pid.to_i) + end + + ruby_processes_pids, non_ruby_processes_pids = pids.partition {|pid| ruby_processes.include? pid} + + DebugPrinter.print_debug("The following child processes was added to attach: #{ruby_processes_pids.join(', ')}") unless ruby_processes_pids.empty? + DebugPrinter.print_debug("The following child are not ruby processes: #{non_ruby_processes_pids.join(', ')}") unless non_ruby_processes_pids.empty? + + ruby_processes_pids end def command_exists(command) @@ -93,4 +112,4 @@ def choose_debugger(ruby_path, pid, gems_to_include, debugger_loader_path, argv) end debugger -end +end \ No newline at end of file From c8dd9490114d14fee8d8d1d3affac29f0424fc67 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Tue, 8 Aug 2017 17:43:36 +0300 Subject: [PATCH 055/104] bump version --- lib/ruby-debug-ide/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index 43e2521..a2776bc 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1.beta7' + IDE_VERSION='0.6.1.beta8' end From 25e81518ebc42f1a97ea058e2ff5defda0c1a320 Mon Sep 17 00:00:00 2001 From: ViugiNick Date: Thu, 10 Aug 2017 17:34:29 +0300 Subject: [PATCH 056/104] set evaluation timeout for child processes(https://youtrack.jetbrains.com/issue/RUBY-19948) --- lib/ruby-debug-ide/multiprocess/pre_child.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/multiprocess/pre_child.rb b/lib/ruby-debug-ide/multiprocess/pre_child.rb index fd0d41f..4d8f65d 100644 --- a/lib/ruby-debug-ide/multiprocess/pre_child.rb +++ b/lib/ruby-debug-ide/multiprocess/pre_child.rb @@ -16,7 +16,8 @@ def pre_child(options = nil) 'tracing' => false, 'int_handler' => true, 'cli_debug' => (ENV['DEBUGGER_CLI_DEBUG'] == 'true'), - 'notify_dispatcher' => true + 'notify_dispatcher' => true, + 'evaluation_timeout' => 10 ) if(options.ignore_port) @@ -43,6 +44,7 @@ def start_debugger(options) # set options Debugger.keep_frame_binding = options.frame_bind Debugger.tracing = options.tracing + Debugger.evaluation_timeout = options.evaluation_timeout Debugger.cli_debug = options.cli_debug Debugger.prepare_debugger(options) end From 802b46aaa94a2aead3ce4b42466959368a02747c Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Tue, 8 Aug 2017 19:18:39 +0300 Subject: [PATCH 057/104] dont use exec_with_allocation for old ruby versions --- lib/ruby-debug-ide/xml_printer.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index aad80f6..e4a92db 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -168,6 +168,8 @@ def print_string(string) end def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) + return value.send exec_method if(RUBY_VERSION < '2.0') + check_memory_limit = true if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) check_memory_limit = false From 6ef0dce3dafa6d40a4a04069b77fa0af2b7cff58 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Mon, 21 Aug 2017 10:54:44 +0300 Subject: [PATCH 058/104] tune encoding version check to formally accept 2.0 origin: RUBY-15434 --- lib/ruby-debug-ide/command.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/command.rb b/lib/ruby-debug-ide/command.rb index 5f099be..dffd1a2 100644 --- a/lib/ruby-debug-ide/command.rb +++ b/lib/ruby-debug-ide/command.rb @@ -118,7 +118,7 @@ def timeout(sec) def debug_eval(str, b = get_binding) begin str = str.to_s - str.force_encoding('UTF-8') if(RUBY_VERSION > '2.0') + str.force_encoding('UTF-8') if(RUBY_VERSION >= '2.0') to_inspect = Command.unescape_incoming(str) max_time = Debugger.evaluation_timeout @printer.print_debug("Evaluating %s with timeout after %i sec", str, max_time) From 4e632dafe6a01e9e19c71e179393511777a966f8 Mon Sep 17 00:00:00 2001 From: ViugiNick Date: Wed, 9 Aug 2017 02:50:29 +0300 Subject: [PATCH 059/104] fix of throwing exceptions in exec_with_allocation_control --- lib/ruby-debug-ide/xml_printer.rb | 60 ++++++++++++++++--------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index aad80f6..ea101d0 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -15,20 +15,24 @@ module OverflowMessageType class MemoryLimitError < StandardError attr_reader :message + attr_reader :trace_point attr_reader :backtrace - def initialize(message, backtrace = []) + def initialize(message, trace_point, backtrace = []) @message = message + @trace_point = trace_point @backtrace = backtrace end end class TimeLimitError < StandardError attr_reader :message + attr_reader :trace_point attr_reader :backtrace - def initialize(message, backtrace = []) + def initialize(message, trace_point, backtrace = []) @message = message + @trace_point = trace_point @backtrace = backtrace end end @@ -168,47 +172,45 @@ def print_string(string) end def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) - check_memory_limit = true - if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) - check_memory_limit = false - end + check_memory_limit = !defined?(JRUBY_VERSION) && ENV['DEBUGGER_MEMORY_LIMIT'].to_i > 0 curr_thread = Thread.current - result = nil - inspect_thread = DebugThread.start { + result = nil + inspect_thread = DebugThread.start do start_alloc_size = ObjectSpace.memsize_of_all if (check_memory_limit) start_time = Time.now.to_f - trace = TracePoint.new(:c_call, :call) do |tp| + trace_point = TracePoint.new(:c_call, :call) do | | + next unless Thread.current == inspect_thread + next unless rand > 0.75 - if (rand > 0.75) - curr_time = Time.now.to_f + curr_time = Time.now.to_f - if ((curr_time - start_time) * 1e3 > time_limit) - curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", caller.to_a) - inspect_thread.kill - end + if (curr_time - start_time) * 1e3 > time_limit + curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", caller.to_a, trace_point) + end - if (check_memory_limit) - curr_alloc_size = ObjectSpace.memsize_of_all - start_alloc_size = curr_alloc_size if (curr_alloc_size < start_alloc_size) + if check_memory_limit + curr_alloc_size = ObjectSpace.memsize_of_all + start_alloc_size = curr_alloc_size if (curr_alloc_size < start_alloc_size) - if (curr_alloc_size - start_alloc_size > 1e6 * memory_limit) - curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", caller.to_a) - inspect_thread.kill - end + if curr_alloc_size - start_alloc_size > 1e6 * memory_limit + curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", caller.to_a, trace_point) end end - end.enable { - result = value.send exec_method - } - } + end + trace_point.enable + result = value.send exec_method + trace_point.disable + end inspect_thread.join - inspect_thread.kill return result rescue MemoryLimitError, TimeLimitError => e - print_debug(e.message + "\n" + e.backtrace.map{|l| "\t#{l}"}.join("\n")) + e.trace_point.disable + print_debug(e.message + "\n" + e.backtrace.map {|l| "\t#{l}"}.join("\n")) return overflow_message_type.call(e) + ensure + inspect_thread.kill if inspect_thread && inspect_thread.alive? end def print_variable(name, value, kind) @@ -512,4 +514,4 @@ def build_value_attr(escaped_value_str) end -end +end \ No newline at end of file From 82ff29e1b5d64d918eaf6637ce1c621a04eb4b39 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Mon, 21 Aug 2017 11:22:55 +0300 Subject: [PATCH 060/104] extract common superclass from TL&ML Errors --- lib/ruby-debug-ide/xml_printer.rb | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index ea101d0..4a26e5f 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -13,7 +13,7 @@ module OverflowMessageType SPECIAL_SYMBOL_MESSAGE = lambda {|e| ''} end - class MemoryLimitError < StandardError + class ExecError < StandardError attr_reader :message attr_reader :trace_point attr_reader :backtrace @@ -25,17 +25,9 @@ def initialize(message, trace_point, backtrace = []) end end - class TimeLimitError < StandardError - attr_reader :message - attr_reader :trace_point - attr_reader :backtrace + class MemoryLimitError < ExecError; end - def initialize(message, trace_point, backtrace = []) - @message = message - @trace_point = trace_point - @backtrace = backtrace - end - end + class TimeLimitError < ExecError; end class XmlPrinter # :nodoc: class ExceptionProxy @@ -205,7 +197,7 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, o end inspect_thread.join return result - rescue MemoryLimitError, TimeLimitError => e + rescue ExecError => e e.trace_point.disable print_debug(e.message + "\n" + e.backtrace.map {|l| "\t#{l}"}.join("\n")) return overflow_message_type.call(e) From c9522b45dfe4673cb75b9c174e8f18e5ffb4b8c1 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Mon, 21 Aug 2017 11:35:18 +0300 Subject: [PATCH 061/104] remove parentheses --- lib/ruby-debug-ide/xml_printer.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 28e962c..87dcb86 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -164,14 +164,14 @@ def print_string(string) end def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) - return value.send exec_method if(RUBY_VERSION < '2.0') + return value.send exec_method if RUBY_VERSION < '2.0' check_memory_limit = !defined?(JRUBY_VERSION) && ENV['DEBUGGER_MEMORY_LIMIT'].to_i > 0 curr_thread = Thread.current result = nil inspect_thread = DebugThread.start do - start_alloc_size = ObjectSpace.memsize_of_all if (check_memory_limit) + start_alloc_size = ObjectSpace.memsize_of_all if check_memory_limit start_time = Time.now.to_f trace_point = TracePoint.new(:c_call, :call) do | | @@ -186,7 +186,7 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, o if check_memory_limit curr_alloc_size = ObjectSpace.memsize_of_all - start_alloc_size = curr_alloc_size if (curr_alloc_size < start_alloc_size) + start_alloc_size = curr_alloc_size if curr_alloc_size < start_alloc_size if curr_alloc_size - start_alloc_size > 1e6 * memory_limit curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", caller.to_a, trace_point) From 4b2c816bf90943ee6708d4709d08b29699147699 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Mon, 21 Aug 2017 11:39:59 +0300 Subject: [PATCH 062/104] fix calls after refactoring --- lib/ruby-debug-ide/xml_printer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 87dcb86..ca60f37 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -181,7 +181,7 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, o curr_time = Time.now.to_f if (curr_time - start_time) * 1e3 > time_limit - curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", caller.to_a, trace_point) + curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", trace_point, caller.to_a) end if check_memory_limit @@ -189,7 +189,7 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, o start_alloc_size = curr_alloc_size if curr_alloc_size < start_alloc_size if curr_alloc_size - start_alloc_size > 1e6 * memory_limit - curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", caller.to_a, trace_point) + curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", trace_point, caller.to_a) end end end From df0314976d2ed7d9678d14f453c9cf2cd49124e8 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Fri, 25 Aug 2017 01:49:55 +0300 Subject: [PATCH 063/104] jruby exec_with_allocation_control heuristic --- lib/ruby-debug-ide/xml_printer.rb | 47 +++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index ca60f37..539483d 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -25,9 +25,19 @@ def initialize(message, trace_point, backtrace = []) end end - class MemoryLimitError < ExecError; end + class JrubyTimeLimitError < StandardError + attr_reader :message - class TimeLimitError < ExecError; end + def initialize(message) + @message = message + end + end + + class MemoryLimitError < ExecError; + end + + class TimeLimitError < ExecError; + end class XmlPrinter # :nodoc: class ExceptionProxy @@ -163,10 +173,28 @@ def print_string(string) end end + def jruby_timeout(sec) + return yield if sec == nil or sec.zero? + if Thread.respond_to?(:critical) and Thread.critical + raise ThreadError, "timeout within critical session" + end + begin + x = Thread.current + y = DebugThread.start { + sleep sec + x.raise JrubyTimeLimitError.new("Timeout: evaluation took longer than #{sec} seconds.") if x.alive? + } + yield sec + ensure + y.kill if y and y.alive? + end + end + def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) return value.send exec_method if RUBY_VERSION < '2.0' - check_memory_limit = !defined?(JRUBY_VERSION) && ENV['DEBUGGER_MEMORY_LIMIT'].to_i > 0 + return jruby_timeout(time_limit/1e3) {value.send exec_method} if defined?(JRUBY_VERSION) + curr_thread = Thread.current result = nil @@ -184,13 +212,11 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, o curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", trace_point, caller.to_a) end - if check_memory_limit - curr_alloc_size = ObjectSpace.memsize_of_all - start_alloc_size = curr_alloc_size if curr_alloc_size < start_alloc_size + curr_alloc_size = ObjectSpace.memsize_of_all + start_alloc_size = curr_alloc_size if curr_alloc_size < start_alloc_size - if curr_alloc_size - start_alloc_size > 1e6 * memory_limit - curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", trace_point, caller.to_a) - end + if curr_alloc_size - start_alloc_size > 1e6 * memory_limit + curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", trace_point, caller.to_a) end end trace_point.enable @@ -203,6 +229,9 @@ def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, o e.trace_point.disable print_debug(e.message + "\n" + e.backtrace.map {|l| "\t#{l}"}.join("\n")) return overflow_message_type.call(e) + rescue JrubyTimeLimitError => e + print_debug(e.message) + return overflow_message_type.call(e) ensure inspect_thread.kill if inspect_thread && inspect_thread.alive? end From a2ea597a83d24a0ef014c50a9729cc0ee4bca688 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Fri, 25 Aug 2017 01:51:34 +0300 Subject: [PATCH 064/104] heuristic also works for old versions --- lib/ruby-debug-ide/xml_printer.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 539483d..db094f0 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -191,9 +191,8 @@ def jruby_timeout(sec) end def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) - return value.send exec_method if RUBY_VERSION < '2.0' - return jruby_timeout(time_limit/1e3) {value.send exec_method} if defined?(JRUBY_VERSION) + return value.send exec_method if RUBY_VERSION < '2.0' curr_thread = Thread.current From 1e9da6d6dbffe7aee2e85ea686b05beadc9ea83e Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Fri, 25 Aug 2017 01:56:57 +0300 Subject: [PATCH 065/104] formatting --- lib/ruby-debug-ide/xml_printer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index db094f0..6d03618 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -191,7 +191,7 @@ def jruby_timeout(sec) end def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) - return jruby_timeout(time_limit/1e3) {value.send exec_method} if defined?(JRUBY_VERSION) + return jruby_timeout(time_limit / 1e3) {value.send exec_method} if defined?(JRUBY_VERSION) return value.send exec_method if RUBY_VERSION < '2.0' curr_thread = Thread.current From 90bbdb6920bbb2ebb46b707af33a29f770e6aa06 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Thu, 31 Aug 2017 19:11:13 +0300 Subject: [PATCH 066/104] new implementation of execution with allocation control --- lib/ruby-debug-ide/xml_printer.rb | 53 +++++++++++++++++-------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 6d03618..d1b4523 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -15,17 +15,15 @@ module OverflowMessageType class ExecError < StandardError attr_reader :message - attr_reader :trace_point attr_reader :backtrace - def initialize(message, trace_point, backtrace = []) + def initialize(message, backtrace = []) @message = message - @trace_point = trace_point @backtrace = backtrace end end - class JrubyTimeLimitError < StandardError + class SimpleTimeLimitError < StandardError attr_reader :message def initialize(message) @@ -173,7 +171,7 @@ def print_string(string) end end - def jruby_timeout(sec) + def exec_with_timeout(sec) return yield if sec == nil or sec.zero? if Thread.respond_to?(:critical) and Thread.critical raise ThreadError, "timeout within critical session" @@ -182,7 +180,7 @@ def jruby_timeout(sec) x = Thread.current y = DebugThread.start { sleep sec - x.raise JrubyTimeLimitError.new("Timeout: evaluation took longer than #{sec} seconds.") if x.alive? + x.raise SimpleTimeLimitError.new("Timeout: evaluation took longer than #{sec} seconds.") if x.alive? } yield sec ensure @@ -191,48 +189,57 @@ def jruby_timeout(sec) end def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) - return jruby_timeout(time_limit / 1e3) {value.send exec_method} if defined?(JRUBY_VERSION) + return exec_with_timeout(time_limit * 1e-3) {value.send exec_method} if defined?(JRUBY_VERSION) || memory_limit <= 0 || (RUBY_VERSION < '2.0' && time_limit > 0) return value.send exec_method if RUBY_VERSION < '2.0' curr_thread = Thread.current + control_thread = Debugger.control_thread result = nil + + trace_queue = Queue.new + inspect_thread = DebugThread.start do - start_alloc_size = ObjectSpace.memsize_of_all if check_memory_limit + start_alloc_size = ObjectSpace.memsize_of_all start_time = Time.now.to_f - trace_point = TracePoint.new(:c_call, :call) do | | - next unless Thread.current == inspect_thread - next unless rand > 0.75 - + trace_point = TracePoint.new(:c_call, :call) do |tp| curr_time = Time.now.to_f if (curr_time - start_time) * 1e3 > time_limit - curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", trace_point, caller.to_a) + trace_queue << TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", caller.to_a) + trace_point.disable + inspect_thread.kill end + next unless rand > 0.75 + curr_alloc_size = ObjectSpace.memsize_of_all start_alloc_size = curr_alloc_size if curr_alloc_size < start_alloc_size if curr_alloc_size - start_alloc_size > 1e6 * memory_limit - curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", trace_point, caller.to_a) + trace_queue << MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", caller.to_a) + trace_point.disable + inspect_thread.kill end end trace_point.enable result = value.send exec_method + trace_queue << result trace_point.disable end - inspect_thread.join - return result - rescue ExecError => e - e.trace_point.disable - print_debug(e.message + "\n" + e.backtrace.map {|l| "\t#{l}"}.join("\n")) - return overflow_message_type.call(e) - rescue JrubyTimeLimitError => e + + while(mes = trace_queue.pop) + if(mes.is_a? TimeLimitError or mes.is_a? MemoryLimitError) + print_debug(mes.message + "\n" + mes.backtrace.map {|l| "\t#{l}"}.join("\n")) + return overflow_message_type.call(mes) + else + return mes + end + end + rescue SimpleTimeLimitError => e print_debug(e.message) return overflow_message_type.call(e) - ensure - inspect_thread.kill if inspect_thread && inspect_thread.alive? end def print_variable(name, value, kind) From 02148f5296b5c30ef0b436358a54217c33e9713c Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Thu, 31 Aug 2017 19:17:23 +0300 Subject: [PATCH 067/104] option to disable 'to_s' tracing added --- bin/rdebug-ide | 49 +++++++++++++++++-------------- lib/ruby-debug-ide/xml_printer.rb | 2 +- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/bin/rdebug-ide b/bin/rdebug-ide index fc03173..f595ebb 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -11,20 +11,20 @@ end $stdout.sync=true options = OpenStruct.new( - 'frame_bind' => false, - 'host' => nil, - 'load_mode' => false, - 'port' => 1234, - 'stop' => false, - 'tracing' => false, - 'int_handler' => true, - 'dispatcher_port' => -1, - 'evaluation_timeout' => 10, - 'rm_protocol_extensions' => false, - 'catchpoint_deleted_event' => false, - 'value_as_nested_element' => false, - 'attach_mode' => false, - 'cli_debug' => false + 'frame_bind' => false, + 'host' => nil, + 'load_mode' => false, + 'port' => 1234, + 'stop' => false, + 'tracing' => false, + 'int_handler' => true, + 'dispatcher_port' => -1, + 'evaluation_timeout' => 10, + 'rm_protocol_extensions' => false, + 'catchpoint_deleted_event' => false, + 'value_as_nested_element' => false, + 'attach_mode' => false, + 'cli_debug' => false ) opts = OptionParser.new do |opts| @@ -36,20 +36,25 @@ Usage: rdebug-ide is supposed to be called from RDT, NetBeans, RubyMine, or EOB opts.separator "" opts.separator "Options:" - + + Debugger.trace_to_s = true + opts.on("--evaluation-control", "trace to_s evaluation") do + Debugger.trace_to_s = true + end + ENV['DEBUGGER_MEMORY_LIMIT'] = '10' - opts.on("-m", "--memory-limit LIMIT", Integer, "evaluation memory limit in mb (default: 10)") do |limit| - ENV['DEBUGGER_MEMORY_LIMIT'] = limit + opts.on("-m", "--memory-limit LIMIT", Integer, "evaluation memory limit in mb (default: 10)") do |limit| + ENV['DEBUGGER_MEMORY_LIMIT'] = limit.to_s end - + ENV['INSPECT_TIME_LIMIT'] = '100' - opts.on("-t", "--time-limit LIMIT", Integer, "evaluation time limit in milliseconds (default: 100)") do |limit| - ENV['INSPECT_TIME_LIMIT'] = limit + opts.on("-t", "--time-limit LIMIT", Integer, "evaluation time limit in milliseconds (default: 100)") do |limit| + ENV['INSPECT_TIME_LIMIT'] = limit.to_s end opts.on("-h", "--host HOST", "Host name used for remote debugging") {|host| options.host = host} opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port} - opts.on("--dispatcher-port PORT", Integer, "Port used for multi-process debugging dispatcher") do |dp| + opts.on("--dispatcher-port PORT", Integer, "Port used for multi-process debugging dispatcher") do |dp| options.dispatcher_port = dp end opts.on('--evaluation-timeout TIMEOUT', Integer,'evaluation timeout in seconds (default: 10)') do |timeout| @@ -133,7 +138,7 @@ if options.dispatcher_port != -1 end Debugger::MultiProcess.do_monkey - ENV['DEBUGGER_STORED_RUBYLIB'] = ENV['RUBYLIB'] + ENV['DEBUGGER_STORED_RUBYLIB'] = ENV['RUBYLIB'] old_opts = ENV['RUBYOPT'] || '' starter = "-r#{File.expand_path(File.dirname(__FILE__))}/../lib/ruby-debug-ide/multiprocess/starter" unless old_opts.include? starter diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index d1b4523..11a5531 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -190,7 +190,7 @@ def exec_with_timeout(sec) def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) return exec_with_timeout(time_limit * 1e-3) {value.send exec_method} if defined?(JRUBY_VERSION) || memory_limit <= 0 || (RUBY_VERSION < '2.0' && time_limit > 0) - return value.send exec_method if RUBY_VERSION < '2.0' + return value.send exec_method if !Debugger.trace_to_s curr_thread = Thread.current control_thread = Debugger.control_thread From a8f6237814b9b6f3f006ee51067b863a37a37e77 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Thu, 31 Aug 2017 19:25:26 +0300 Subject: [PATCH 068/104] option to disable 'to_s' tracing added --- lib/ruby-debug-ide.rb | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/ruby-debug-ide.rb b/lib/ruby-debug-ide.rb index d100fe5..0198c09 100644 --- a/lib/ruby-debug-ide.rb +++ b/lib/ruby-debug-ide.rb @@ -29,22 +29,22 @@ def print_debug(*args) end def cleanup_backtrace(backtrace) - cleared = [] - return cleared unless backtrace - backtrace.each do |line| - if line.index(File.expand_path(File.dirname(__FILE__) + "/..")) == 0 - next - end - if line.index("-e:1") == 0 - break - end - cleared << line - end - cleared + cleared = [] + return cleared unless backtrace + backtrace.each do |line| + if line.index(File.expand_path(File.dirname(__FILE__) + "/..")) == 0 + next + end + if line.index("-e:1") == 0 + break + end + cleared << line + end + cleared end attr_accessor :attached - attr_accessor :cli_debug, :xml_debug, :evaluation_timeout + attr_accessor :cli_debug, :xml_debug, :evaluation_timeout, :trace_to_s attr_accessor :control_thread attr_reader :interface # protocol extensions From 7a6edb39e039c604afb43d742b121d0c2b143cd0 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Thu, 31 Aug 2017 19:31:23 +0300 Subject: [PATCH 069/104] cleanup --- bin/rdebug-ide | 2 +- lib/ruby-debug-ide/xml_printer.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/rdebug-ide b/bin/rdebug-ide index f595ebb..63a10fd 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -37,7 +37,7 @@ EOB opts.separator "" opts.separator "Options:" - Debugger.trace_to_s = true + Debugger.trace_to_s = false opts.on("--evaluation-control", "trace to_s evaluation") do Debugger.trace_to_s = true end diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 11a5531..d453e9a 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -189,8 +189,9 @@ def exec_with_timeout(sec) end def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) - return exec_with_timeout(time_limit * 1e-3) {value.send exec_method} if defined?(JRUBY_VERSION) || memory_limit <= 0 || (RUBY_VERSION < '2.0' && time_limit > 0) return value.send exec_method if !Debugger.trace_to_s + return exec_with_timeout(time_limit * 1e-3) {value.send exec_method} if defined?(JRUBY_VERSION) || memory_limit <= 0 || (RUBY_VERSION < '2.0' && time_limit > 0) + curr_thread = Thread.current control_thread = Debugger.control_thread From 80b1e8b7f31b706ed4a81fc6bded9c311b037eb0 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Thu, 31 Aug 2017 19:54:35 +0300 Subject: [PATCH 070/104] useless inheritance deleted --- lib/ruby-debug-ide/xml_printer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index d453e9a..3af4baa 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -13,7 +13,7 @@ module OverflowMessageType SPECIAL_SYMBOL_MESSAGE = lambda {|e| ''} end - class ExecError < StandardError + class ExecError attr_reader :message attr_reader :backtrace From c9361b192db54839caa4f95068a76970013178de Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Thu, 31 Aug 2017 20:55:52 +0300 Subject: [PATCH 071/104] error message for exec_with_timeout changed --- lib/ruby-debug-ide/xml_printer.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 3af4baa..30801d1 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -171,7 +171,7 @@ def print_string(string) end end - def exec_with_timeout(sec) + def exec_with_timeout(sec, error_message) return yield if sec == nil or sec.zero? if Thread.respond_to?(:critical) and Thread.critical raise ThreadError, "timeout within critical session" @@ -180,7 +180,7 @@ def exec_with_timeout(sec) x = Thread.current y = DebugThread.start { sleep sec - x.raise SimpleTimeLimitError.new("Timeout: evaluation took longer than #{sec} seconds.") if x.alive? + x.raise SimpleTimeLimitError.new(error_message) if x.alive? } yield sec ensure @@ -190,7 +190,7 @@ def exec_with_timeout(sec) def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) return value.send exec_method if !Debugger.trace_to_s - return exec_with_timeout(time_limit * 1e-3) {value.send exec_method} if defined?(JRUBY_VERSION) || memory_limit <= 0 || (RUBY_VERSION < '2.0' && time_limit > 0) + return exec_with_timeout(time_limit * 1e-3, "Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.") {value.send exec_method} if defined?(JRUBY_VERSION) || memory_limit <= 0 || (RUBY_VERSION < '2.0' && time_limit > 0) curr_thread = Thread.current From 17a5371902a6cfe5e0ff31f2bfa61b13b27a13e6 Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Fri, 1 Sep 2017 14:45:30 +0300 Subject: [PATCH 072/104] bump --- lib/ruby-debug-ide/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index a2776bc..deb8ad5 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1.beta8' + IDE_VERSION='0.6.1.beta9' end From efef1e502376b74b2789d5185a1987265bcf1627 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Wed, 13 Sep 2017 04:17:50 +0300 Subject: [PATCH 073/104] Add official JB badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9e4024d..90df01e 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,5 @@ # ruby-debug-ide An interface which glues ruby-debug to IDEs like Eclipse (RDT), NetBeans and RubyMine. +[![official JetBrains project](http://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![Gem Version](https://badge.fury.io/rb/ruby-debug-ide.svg)][gem] From f67c733db1e649f0f54b24acdebd6ec03f5554f5 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Tue, 7 Nov 2017 14:10:34 +0300 Subject: [PATCH 074/104] changed require 'objectspace' to include ObjectSpace --- lib/ruby-debug-ide/xml_printer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 30801d1..1f9f234 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -2,7 +2,7 @@ require 'cgi' require 'monitor' if (!defined?(JRUBY_VERSION)) - require 'objspace' + include ObjectSpace end module Debugger From fe9ed572f051208ab2a3b4c1ef8c81bf13f9f6d4 Mon Sep 17 00:00:00 2001 From: ViugiNick Date: Tue, 14 Nov 2017 15:27:16 +0300 Subject: [PATCH 075/104] 2.5.0 does not produce an empty statement (#127) Now new line token is a condition for continued command, so the whitespace was added to the end of the command https://github.com/ruby/ruby/commit/f14f0d34641ece73d77781856eed70de760f9d9f --- lib/ruby-debug-ide/commands/expression_info.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/commands/expression_info.rb b/lib/ruby-debug-ide/commands/expression_info.rb index 795ca2d..a03bc83 100644 --- a/lib/ruby-debug-ide/commands/expression_info.rb +++ b/lib/ruby-debug-ide/commands/expression_info.rb @@ -9,7 +9,7 @@ def regexp end def execute - string_to_parse = Command.unescape_incoming(@match.post_match) + "\n\n\n" + string_to_parse = Command.unescape_incoming(@match.post_match) + " \n\n\n" total_lines = string_to_parse.count("\n") + 1 lexer = RubyLex.new From 92ffd69e3cb8f473dc2accc32fd35cb20e4d6b56 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Tue, 14 Nov 2017 18:48:27 +0300 Subject: [PATCH 076/104] bump version --- lib/ruby-debug-ide/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index deb8ad5..6aca7c7 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1.beta9' + IDE_VERSION='0.6.1.beta10' end From 1faaa9877b4bd9c0b8d7602b7602c5c273f49ccf Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Thu, 16 Nov 2017 15:08:04 +0300 Subject: [PATCH 077/104] move evaluation control opts near eval timeout --- bin/rdebug-ide | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bin/rdebug-ide b/bin/rdebug-ide index 63a10fd..1843b35 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -37,6 +37,14 @@ EOB opts.separator "" opts.separator "Options:" + opts.on("-h", "--host HOST", "Host name used for remote debugging") {|host| options.host = host} + opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port} + opts.on("--dispatcher-port PORT", Integer, "Port used for multi-process debugging dispatcher") do |dp| + options.dispatcher_port = dp + end + opts.on('--evaluation-timeout TIMEOUT', Integer,'evaluation timeout in seconds (default: 10)') do |timeout| + options.evaluation_timeout = timeout + end Debugger.trace_to_s = false opts.on("--evaluation-control", "trace to_s evaluation") do Debugger.trace_to_s = true @@ -52,14 +60,6 @@ EOB ENV['INSPECT_TIME_LIMIT'] = limit.to_s end - opts.on("-h", "--host HOST", "Host name used for remote debugging") {|host| options.host = host} - opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port} - opts.on("--dispatcher-port PORT", Integer, "Port used for multi-process debugging dispatcher") do |dp| - options.dispatcher_port = dp - end - opts.on('--evaluation-timeout TIMEOUT', Integer,'evaluation timeout in seconds (default: 10)') do |timeout| - options.evaluation_timeout = timeout - end opts.on('--stop', 'stop when the script is loaded') {options.stop = true} opts.on("-x", "--trace", "turn on line tracing") {options.tracing = true} opts.on("-l", "--load-mode", "load mode (experimental)") {options.load_mode = true} From cfb1f0616898d99aeef820a3b545d84a2f0bd1c5 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Thu, 16 Nov 2017 16:56:24 +0300 Subject: [PATCH 078/104] unify time/memlimit opts with other ones; fix objspace req --- bin/rdebug-ide | 23 +++++++++++------ lib/ruby-debug-ide.rb | 3 ++- lib/ruby-debug-ide/multiprocess/pre_child.rb | 8 +++++- lib/ruby-debug-ide/xml_printer.rb | 27 ++++++++------------ 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/bin/rdebug-ide b/bin/rdebug-ide index 1843b35..a72dfd1 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -20,6 +20,9 @@ options = OpenStruct.new( 'int_handler' => true, 'dispatcher_port' => -1, 'evaluation_timeout' => 10, + 'trace_to_s' => false, + 'debugger_memory_limit' => 10, + 'inspect_time_limit' => 100, 'rm_protocol_extensions' => false, 'catchpoint_deleted_event' => false, 'value_as_nested_element' => false, @@ -45,19 +48,20 @@ EOB opts.on('--evaluation-timeout TIMEOUT', Integer,'evaluation timeout in seconds (default: 10)') do |timeout| options.evaluation_timeout = timeout end - Debugger.trace_to_s = false - opts.on("--evaluation-control", "trace to_s evaluation") do - Debugger.trace_to_s = true - end + opts.on("--evaluation-control", "trace to_s evaluation") {options.trace_to_s = true} - ENV['DEBUGGER_MEMORY_LIMIT'] = '10' opts.on("-m", "--memory-limit LIMIT", Integer, "evaluation memory limit in mb (default: 10)") do |limit| - ENV['DEBUGGER_MEMORY_LIMIT'] = limit.to_s + if defined?(JRUBY_VERSION) || RUBY_VERSION < '2.0' + $stderr.puts "Evaluation memory limit is ineffective in JRuby and MRI < 2.0" + limit = 0 + end + options.debugger_memory_limit = limit + options.trace_to_s ||= limit > 0 end - ENV['INSPECT_TIME_LIMIT'] = '100' opts.on("-t", "--time-limit LIMIT", Integer, "evaluation time limit in milliseconds (default: 100)") do |limit| - ENV['INSPECT_TIME_LIMIT'] = limit.to_s + options.inspect_time_limit = limit + options.trace_to_s ||= limit > 0 end opts.on('--stop', 'stop when the script is loaded') {options.stop = true} @@ -157,6 +161,9 @@ end Debugger.keep_frame_binding = options.frame_bind Debugger.tracing = options.tracing Debugger.evaluation_timeout = options.evaluation_timeout +Debugger.trace_to_s = options.trace_to_s && (options.debugger_memory_limit > 0 || options.inspect_time_limit > 0) +Debugger.debugger_memory_limit = options.debugger_memory_limit +Debugger.inspect_time_limit = options.inspect_time_limit Debugger.catchpoint_deleted_event = options.catchpoint_deleted_event || options.rm_protocol_extensions Debugger.value_as_nested_element = options.value_as_nested_element || options.rm_protocol_extensions diff --git a/lib/ruby-debug-ide.rb b/lib/ruby-debug-ide.rb index 0198c09..7482da3 100644 --- a/lib/ruby-debug-ide.rb +++ b/lib/ruby-debug-ide.rb @@ -44,7 +44,8 @@ def cleanup_backtrace(backtrace) end attr_accessor :attached - attr_accessor :cli_debug, :xml_debug, :evaluation_timeout, :trace_to_s + attr_accessor :cli_debug, :xml_debug, :evaluation_timeout + attr_accessor :trace_to_s, :debugger_memory_limit, :inspect_time_limit attr_accessor :control_thread attr_reader :interface # protocol extensions diff --git a/lib/ruby-debug-ide/multiprocess/pre_child.rb b/lib/ruby-debug-ide/multiprocess/pre_child.rb index 4d8f65d..4a349fd 100644 --- a/lib/ruby-debug-ide/multiprocess/pre_child.rb +++ b/lib/ruby-debug-ide/multiprocess/pre_child.rb @@ -17,7 +17,10 @@ def pre_child(options = nil) 'int_handler' => true, 'cli_debug' => (ENV['DEBUGGER_CLI_DEBUG'] == 'true'), 'notify_dispatcher' => true, - 'evaluation_timeout' => 10 + 'evaluation_timeout' => 10, + 'trace_to_s' => false, + 'debugger_memory_limit' => 10, + 'inspect_time_limit' => 100 ) if(options.ignore_port) @@ -45,6 +48,9 @@ def start_debugger(options) Debugger.keep_frame_binding = options.frame_bind Debugger.tracing = options.tracing Debugger.evaluation_timeout = options.evaluation_timeout + Debugger.trace_to_s = options.trace_to_s + Debugger.debugger_memory_limit = options.debugger_memory_limit + Debugger.inspect_time_limit = options.inspect_time_limit Debugger.cli_debug = options.cli_debug Debugger.prepare_debugger(options) end diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 1f9f234..8014715 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -1,9 +1,6 @@ require 'stringio' require 'cgi' require 'monitor' -if (!defined?(JRUBY_VERSION)) - include ObjectSpace -end module Debugger @@ -153,7 +150,7 @@ def print_hash(hash) if k.class.name == "String" name = '\'' + k + '\'' else - name = exec_with_allocation_control(k, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) + name = exec_with_allocation_control(k, Debugger.debugger_memory_limit, Debugger.inspect_time_limit, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) end print_variable(name, hash[k], 'instance') } @@ -189,18 +186,16 @@ def exec_with_timeout(sec, error_message) end def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) - return value.send exec_method if !Debugger.trace_to_s - return exec_with_timeout(time_limit * 1e-3, "Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.") {value.send exec_method} if defined?(JRUBY_VERSION) || memory_limit <= 0 || (RUBY_VERSION < '2.0' && time_limit > 0) + return value.send exec_method unless Debugger.trace_to_s + if defined?(JRUBY_VERSION) || RUBY_VERSION < '2.0' || memory_limit <= 0 + return exec_with_timeout(time_limit * 1e-3, "Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.") { value.send exec_method } + end - curr_thread = Thread.current - control_thread = Debugger.control_thread - - result = nil - + require 'objspace' trace_queue = Queue.new - inspect_thread = DebugThread.start do + DebugThread.start do start_alloc_size = ObjectSpace.memsize_of_all start_time = Time.now.to_f @@ -263,7 +258,7 @@ def print_variable(name, value, kind) else has_children = !value.instance_variables.empty? || !value.class.class_variables.empty? - value_str = exec_with_allocation_control(value, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) || 'nil' rescue "<#to_s method raised exception: #{$!}>" + value_str = exec_with_allocation_control(value, Debugger.debugger_memory_limit, Debugger.inspect_time_limit, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) || 'nil' rescue "<#to_s method raised exception: #{$!}>" unless value_str.is_a?(String) value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String." end @@ -489,7 +484,7 @@ def max_compact_name_size def compact_array_str(value) slice = value[0..10] - compact = exec_with_allocation_control(slice, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :inspect, OverflowMessageType::NIL_MESSAGE) + compact = exec_with_allocation_control(slice, Debugger.debugger_memory_limit, Debugger.inspect_time_limit, :inspect, OverflowMessageType::NIL_MESSAGE) if compact && value.size != slice.size compact[0..compact.size - 2] + ", ...]" @@ -501,14 +496,14 @@ def compact_hash_str(value) keys_strings = Hash.new slice = value.sort_by do |k, _| - keys_string = exec_with_allocation_control(k, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE) + keys_string = exec_with_allocation_control(k, Debugger.debugger_memory_limit, Debugger.inspect_time_limit, :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE) keys_strings[k] = keys_string keys_string end[0..5] compact = slice.map do |kv| key_string = keys_strings[kv[0]] - value_string = exec_with_allocation_control(kv[1], ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE) + value_string = exec_with_allocation_control(kv[1], Debugger.debugger_memory_limit, Debugger.inspect_time_limit, :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE) "#{key_string}: #{handle_binary_data(value_string)}" end.join(", ") "{" + compact + (slice.size != value.size ? ", ..." : "") + "}" From 845abb10fe07bb867cf20acac1ca81c584382ee8 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Thu, 16 Nov 2017 16:58:54 +0300 Subject: [PATCH 079/104] remove constant parameters --- lib/ruby-debug-ide/xml_printer.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 8014715..0ad7d00 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -150,7 +150,7 @@ def print_hash(hash) if k.class.name == "String" name = '\'' + k + '\'' else - name = exec_with_allocation_control(k, Debugger.debugger_memory_limit, Debugger.inspect_time_limit, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) + name = exec_with_allocation_control(k, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) end print_variable(name, hash[k], 'instance') } @@ -185,9 +185,12 @@ def exec_with_timeout(sec, error_message) end end - def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type) + def exec_with_allocation_control(value, exec_method, overflow_message_type) return value.send exec_method unless Debugger.trace_to_s + memory_limit = Debugger.debugger_memory_limit + time_limit = Debugger.inspect_time_limit + if defined?(JRUBY_VERSION) || RUBY_VERSION < '2.0' || memory_limit <= 0 return exec_with_timeout(time_limit * 1e-3, "Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.") { value.send exec_method } end @@ -258,7 +261,7 @@ def print_variable(name, value, kind) else has_children = !value.instance_variables.empty? || !value.class.class_variables.empty? - value_str = exec_with_allocation_control(value, Debugger.debugger_memory_limit, Debugger.inspect_time_limit, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) || 'nil' rescue "<#to_s method raised exception: #{$!}>" + value_str = exec_with_allocation_control(value, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) || 'nil' rescue "<#to_s method raised exception: #{$!}>" unless value_str.is_a?(String) value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String." end @@ -484,7 +487,7 @@ def max_compact_name_size def compact_array_str(value) slice = value[0..10] - compact = exec_with_allocation_control(slice, Debugger.debugger_memory_limit, Debugger.inspect_time_limit, :inspect, OverflowMessageType::NIL_MESSAGE) + compact = exec_with_allocation_control(slice, :inspect, OverflowMessageType::NIL_MESSAGE) if compact && value.size != slice.size compact[0..compact.size - 2] + ", ...]" @@ -496,14 +499,14 @@ def compact_hash_str(value) keys_strings = Hash.new slice = value.sort_by do |k, _| - keys_string = exec_with_allocation_control(k, Debugger.debugger_memory_limit, Debugger.inspect_time_limit, :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE) + keys_string = exec_with_allocation_control(k, :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE) keys_strings[k] = keys_string keys_string end[0..5] compact = slice.map do |kv| key_string = keys_strings[kv[0]] - value_string = exec_with_allocation_control(kv[1], Debugger.debugger_memory_limit, Debugger.inspect_time_limit, :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE) + value_string = exec_with_allocation_control(kv[1], :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE) "#{key_string}: #{handle_binary_data(value_string)}" end.join(", ") "{" + compact + (slice.size != value.size ? ", ..." : "") + "}" From c24646fc158de2d770263a056f18484d0d7b6bee Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Thu, 16 Nov 2017 17:23:27 +0300 Subject: [PATCH 080/104] add dependency on debase/ruby-debug-base --- ruby-debug-ide.gemspec | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ruby-debug-ide.gemspec b/ruby-debug-ide.gemspec index 7cd29ef..4c974f3 100644 --- a/ruby-debug-ide.gemspec +++ b/ruby-debug-ide.gemspec @@ -42,6 +42,12 @@ EOF spec.extensions << "ext/mkrf_conf.rb" unless ENV['NO_EXT'] spec.add_dependency("rake", ">= 0.8.1") + if RUBY_VERSION < '2.0' || defined?(JRUBY_VERSION) + spec.add_dependency 'ruby-debug-base' + else + spec.add_dependency 'debase', '~> 0.2.2.beta11' + end + spec.required_ruby_version = '>= 1.8.2' spec.date = DateTime.now spec.rubyforge_project = 'debug-commons' From fca6ea4f96157a9176ade92e1ad7af5c21c8cbaf Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Thu, 16 Nov 2017 18:03:31 +0300 Subject: [PATCH 081/104] Revert "add dependency on debase/ruby-debug-base" This reverts commit c24646f --- ruby-debug-ide.gemspec | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ruby-debug-ide.gemspec b/ruby-debug-ide.gemspec index 4c974f3..7cd29ef 100644 --- a/ruby-debug-ide.gemspec +++ b/ruby-debug-ide.gemspec @@ -42,12 +42,6 @@ EOF spec.extensions << "ext/mkrf_conf.rb" unless ENV['NO_EXT'] spec.add_dependency("rake", ">= 0.8.1") - if RUBY_VERSION < '2.0' || defined?(JRUBY_VERSION) - spec.add_dependency 'ruby-debug-base' - else - spec.add_dependency 'debase', '~> 0.2.2.beta11' - end - spec.required_ruby_version = '>= 1.8.2' spec.date = DateTime.now spec.rubyforge_project = 'debug-commons' From 58985e579341ec30775e4f39ac0af30fa7818564 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Mon, 4 Dec 2017 19:14:04 +0300 Subject: [PATCH 082/104] revert unlucky variable removal --- lib/ruby-debug-ide/xml_printer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 0ad7d00..de425f5 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -198,7 +198,7 @@ def exec_with_allocation_control(value, exec_method, overflow_message_type) require 'objspace' trace_queue = Queue.new - DebugThread.start do + inspect_thread = DebugThread.start do start_alloc_size = ObjectSpace.memsize_of_all start_time = Time.now.to_f From bf7f78e38af8466572e5a314585e98ff13dad7ae Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Tue, 5 Dec 2017 19:32:38 +0300 Subject: [PATCH 083/104] bump version to beta11 --- lib/ruby-debug-ide/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index 6aca7c7..3287e97 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1.beta10' + IDE_VERSION='0.6.1.beta11' end From f6ece22511b30b98523332ab151a26873fc1e423 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Tue, 21 Nov 2017 22:23:32 +0300 Subject: [PATCH 084/104] tests fixes (cherry picked from commit ac9e8b1) (cherry picked from commit a492b11) --- Gemfile | 1 + lib/ruby-debug-ide.rb | 2 +- test-base/test_base.rb | 5 ++++- test-base/threads_and_frames_test.rb | 18 +++++++++++++++--- test-base/variables_test.rb | 11 +++++++---- test/ruby-debug/xml_printer_test.rb | 9 ++++++--- 6 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 2ee4fb3..78ab332 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,7 @@ source "http://rubygems.org/" gem "ruby-debug-base", :platforms => [:jruby, :ruby_18, :mingw_18] gem "ruby-debug-base19x", ">= 0.11.30.pre4", :platforms => [:ruby_19, :mingw_19] +gem "debase", ">= 0.2.2.beta11", :platforms => [:ruby_20, :mingw_20, :ruby_21, :mingw_21, :ruby_22, :mingw_22, :ruby_23, :mingw_23, :ruby_24, :mingw_24] gemspec diff --git a/lib/ruby-debug-ide.rb b/lib/ruby-debug-ide.rb index 7482da3..62c0671 100644 --- a/lib/ruby-debug-ide.rb +++ b/lib/ruby-debug-ide.rb @@ -112,7 +112,7 @@ def start_control(host, port, notify_dispatcher) # "localhost" and nil have problems on some systems. host ||= '127.0.0.1' server = TCPServer.new(host, port) - print_greeting_msg($stderr, host, port) + print_greeting_msg($stderr, host, port) if defined? IDE_VERSION notify_dispatcher(port) if notify_dispatcher while (session = server.accept) $stderr.puts "Connected from #{session.peeraddr[2]}" if Debugger.cli_debug diff --git a/test-base/test_base.rb b/test-base/test_base.rb index 3399c8b..afc3ab3 100644 --- a/test-base/test_base.rb +++ b/test-base/test_base.rb @@ -86,7 +86,7 @@ def start_ruby_process(script) @port = TestBase.find_free_port cmd = debug_command(script, @port) debug "Starting: #{cmd}\n" - + Thread.new do if RUBY_VERSION < '1.9' (_, p_out, p_err) = Open3.popen3(cmd) @@ -222,7 +222,9 @@ def assert_suspension(exp_file, exp_line, exp_frames, exp_thread_id=1) suspension = read_suspension assert_equal(exp_file, suspension.file) assert_equal(exp_line, suspension.line) + exp_frames += 2 if Debugger::FRONT_END == "debase" assert_equal(exp_frames, suspension.frames) + exp_thread_id += 1 if Debugger::FRONT_END == "debase" assert_equal(exp_thread_id, suspension.threadId) end @@ -271,6 +273,7 @@ def assert_exception(exp_file, exp_line, exp_type, exp_thread_id=1) assert_equal(exp_file, exception.file) assert_equal(exp_line, exception.line) assert_equal(exp_type, exception.type) + exp_thread_id += 1 if Debugger::FRONT_END == "debase" assert_equal(exp_thread_id, exception.threadId) assert_not_nil(exception.message) end diff --git a/test-base/threads_and_frames_test.rb b/test-base/threads_and_frames_test.rb index c96b19a..e04cc8f 100644 --- a/test-base/threads_and_frames_test.rb +++ b/test-base/threads_and_frames_test.rb @@ -14,7 +14,11 @@ def test_frames assert_breakpoint_added_no(2) send_ruby("w") frames = read_frames - assert_equal(2, frames.length) + + needed_frame_length = 2 + needed_frame_length += 2 if Debugger::FRONT_END == "debase" + assert_equal(needed_frame_length, frames.length) + frame1 = frames[0] assert_equal(@test2_path, frame1.file) assert_equal(1, frame1.no) @@ -27,7 +31,11 @@ def test_frames assert_test_breakpoint(4) send_ruby("w") frames = read_frames - assert_equal(1, frames.length) + + needed_frame_length = 1 + needed_frame_length += 2 if Debugger::FRONT_END == "debase" + + assert_equal(needed_frame_length, frames.length) send_cont # test:4 -> test2:3 assert_breakpoint("test2.rb", 3) send_cont # test2:3 -> finish @@ -42,7 +50,11 @@ def test_frames_when_thread_spawned "def calc", "5 + 5", "end", "start_thread()", "calc()"] run_to_line(5) send_ruby("w") - assert_equal(2, read_frames.length) + + needed_length = 2 + needed_length += 2 if Debugger::FRONT_END == "debase" + + assert_equal(needed_length, read_frames.length) send_cont end diff --git a/test-base/variables_test.rb b/test-base/variables_test.rb index e0d3140..7586050 100644 --- a/test-base/variables_test.rb +++ b/test-base/variables_test.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# encoding: utf-8 $:.unshift File.join(File.dirname(__FILE__), "..", "lib") @@ -25,7 +26,7 @@ def test_variable_with_xml_content {:name => "stringA"}, {:name => "testHashValue"}) # will receive '' - assert_equal("", variables[0].value) + assert_equal(CGI.escapeHTML(""), variables[0].value) assert_local(variables[0]) # the testHashValue contains an example, where the name consists of special # characters @@ -99,10 +100,12 @@ def test_variable_local end def test_variable_instance - create_socket ["require 'test2.rb'", "custom_object=Test2.new", "puts custom_object"] + create_socket ["require_relative 'test2.rb'", "custom_object=Test2.new", "puts custom_object"] create_test2 ["class Test2", "def initialize", "@y=5", "end", "def to_s", "'test'", "end", "end"] run_to("test2.rb", 6) - send_ruby("frame 3; v i custom_object") + frame_number = 3 + frame_number -= 1 if Debugger::FRONT_END == "debase" + send_ruby("frame #{frame_number}; v i custom_object") assert_variables(read_variables, 1, {:name => "@y", :value => "5", :type => "Fixnum", :hasChildren => false}) send_cont @@ -128,7 +131,7 @@ def test_variable_hash_with_string_keys {:name => "hash", :hasChildren => true}) send_ruby("v i hash") assert_variables(read_variables, 2, - {:name => "'a'", :value => "z", :type => "String"}) + {:name => CGI.escape_html("'a'"), :value => "z", :type => "String"}) send_cont end diff --git a/test/ruby-debug/xml_printer_test.rb b/test/ruby-debug/xml_printer_test.rb index 08eb537..ab58014 100644 --- a/test/ruby-debug/xml_printer_test.rb +++ b/test/ruby-debug/xml_printer_test.rb @@ -48,8 +48,8 @@ def test_print_frames test_path = File.join(Dir.pwd, 'test.rb') expected = [ "", - "", - "", + "", + "", ""] assert_equal(expected, interface.data) end @@ -64,7 +64,10 @@ def test_print_at_line Debugger.stop end test_path = File.join(Dir.pwd, 'test.rb') - expected = [""] + + #TODO investigate + expected = [""] + assert_equal(expected, interface.data) end From 2c5096cab3cbb364d73a5602e9621a5c1c4f1a1e Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Thu, 23 Nov 2017 23:48:39 +0300 Subject: [PATCH 085/104] additional require debase from command.rb Sometimes it is evaluating before the ruby-debug-ide.rb, from where it normally requires --- lib/ruby-debug-ide/command.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/ruby-debug-ide/command.rb b/lib/ruby-debug-ide/command.rb index dffd1a2..e6ee3e1 100644 --- a/lib/ruby-debug-ide/command.rb +++ b/lib/ruby-debug-ide/command.rb @@ -1,3 +1,9 @@ +if RUBY_VERSION < '2.0' || defined?(JRUBY_VERSION) + require 'ruby-debug-base' +else + require 'debase' +end + require 'ruby-debug-ide/helper' require 'delegate' From 9c32ff423f2add09b63355cc2a3f975c6a232376 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Thu, 7 Dec 2017 18:19:27 +0300 Subject: [PATCH 086/104] added `test_to_s_timelimit` test --- test-base/variables_test.rb | 28 ++++++++++++++++++++++++++++ test/rd_test_base.rb | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/test-base/variables_test.rb b/test-base/variables_test.rb index 7586050..7ab798b 100644 --- a/test-base/variables_test.rb +++ b/test-base/variables_test.rb @@ -199,6 +199,34 @@ def test_to_s_raises_exception send_cont end + def test_to_s_timelimit + create_socket ['class A', + 'def to_s', + 'a = 1', + 'loop do', + 'a = a + 1', + 'sleep 1', + 'break if (a > 2)', + 'end', + 'a.to_s', + 'end', + 'end', + 'b = Hash.new', + 'b[A.new] = A.new', + 'b[1] = A.new', + 'puts b #bp here'] + run_to_line(15) + send_ruby('v l') + assert_variables(read_variables, 1, + {:name => "b", :value => "Hash (2 elements)", :type => "Hash"}) + + send_ruby("v i b") + assert_variables(read_variables, 2, + {:name => "Timeout: evaluation of to_s took longer than 100ms.", :value => "Timeout: evaluation of to_s took longer than 100ms.", :type => "A"}, + {:name => "1", :value => "Timeout: evaluation of to_s took longer than 100ms.", :type => "A"}) + send_cont + end + def assert_xml(expected_xml, actual_xml) # XXX is there a better way then html_escape in standard libs? assert_equal(ERB::Util.html_escape(expected_xml), actual_xml) diff --git a/test/rd_test_base.rb b/test/rd_test_base.rb index d872f4e..7d29edc 100644 --- a/test/rd_test_base.rb +++ b/test/rd_test_base.rb @@ -24,7 +24,7 @@ def debug_command(script, port) cmd << " -J-Xdebug -J-Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y" if jruby? and debug_jruby? cmd << " -I 'lib:#{File.dirname(script)}' #{@rdebug_ide}" + (@verbose_server ? " -d" : "") + - " -p #{port} -- '#{script}'" + " -p #{port} --evaluation-control --time-limit 100 --memory-limit 0 -- '#{script}'" end def start_debugger From 9b2a4de5b4b33cf1be9af00e1e49dc685f3da4df Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Mon, 11 Dec 2017 23:48:20 +0300 Subject: [PATCH 087/104] update debase dependency to use opened attributes for tests --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 78ab332..c2689bf 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source "http://rubygems.org/" gem "ruby-debug-base", :platforms => [:jruby, :ruby_18, :mingw_18] gem "ruby-debug-base19x", ">= 0.11.30.pre4", :platforms => [:ruby_19, :mingw_19] -gem "debase", ">= 0.2.2.beta11", :platforms => [:ruby_20, :mingw_20, :ruby_21, :mingw_21, :ruby_22, :mingw_22, :ruby_23, :mingw_23, :ruby_24, :mingw_24] +gem "debase", ">= 0.2.2.beta12", :platforms => [:ruby_20, :mingw_20, :ruby_21, :mingw_21, :ruby_22, :mingw_22, :ruby_23, :mingw_23, :ruby_24, :mingw_24] gemspec From dd819a0b2de93486cfe8811bf86cd7cb54ca4080 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Tue, 12 Dec 2017 00:00:49 +0300 Subject: [PATCH 088/104] expect actual Fixnum/Integer class name as answer --- test-base/variables_test.rb | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test-base/variables_test.rb b/test-base/variables_test.rb index 7ab798b..67f9b48 100644 --- a/test-base/variables_test.rb +++ b/test-base/variables_test.rb @@ -47,7 +47,7 @@ def test_variable_in_object {:name => "self", :value => "test", :type => "Test", :hasChildren => true}) send_ruby("v i self") assert_variables(read_variables, 1, - {:name => "@y", :value => "5", :type => "Fixnum", :hasChildren => false, :kind => "instance"}) + {:name => "@y", :value => "5", :type => int_type_name, :hasChildren => false, :kind => "instance"}) send_cont end @@ -60,7 +60,7 @@ def test_class_variables {:name => "self", :hasChildren => true}) send_ruby("v i self") assert_variables(read_variables, 1, - {:name => "@@class_var", :value => "55", :type => "Fixnum", :kind => "class"}) + {:name => "@@class_var", :value => "55", :type => int_type_name, :kind => "class"}) send_cont end @@ -70,7 +70,7 @@ def test_singleton_class_variables run_to_line(3) send_ruby("v i self") assert_variables(read_variables, 1, - {:name => "@@class_var", :value => "55", :type => "Fixnum", :hasChildren => false, :kind => "class"}) + {:name => "@@class_var", :value => "55", :type => int_type_name, :hasChildren => false, :kind => "class"}) send_cont end @@ -95,7 +95,7 @@ def test_variable_local assert_not_nil variables[1].objectId send_ruby("v i " + variables[1].objectId) # 'user' variable assert_variables(read_variables, 1, - {:name => "@id", :value => "22", :type => "Fixnum", :hasChildren => false}) + {:name => "@id", :value => "22", :type => int_type_name, :hasChildren => false}) send_cont end @@ -107,7 +107,7 @@ def test_variable_instance frame_number -= 1 if Debugger::FRONT_END == "debase" send_ruby("frame #{frame_number}; v i custom_object") assert_variables(read_variables, 1, - {:name => "@y", :value => "5", :type => "Fixnum", :hasChildren => false}) + {:name => "@y", :value => "5", :type => int_type_name, :hasChildren => false}) send_cont end @@ -119,7 +119,7 @@ def test_variable_array {:name => "array", :type => "Array", :hasChildren => true}) send_ruby("v i array") assert_variables(read_variables, 2, - {:name => "[0]", :value => "1", :type => "Fixnum"}) + {:name => "[0]", :value => "1", :type => int_type_name}) send_cont end @@ -152,7 +152,7 @@ def test_variable_hash_with_object_keys # get the value send_ruby("frame 1 ; v i " + elements[0].objectId) assert_variables(read_variables, 1, - {:name => "@a", :value => "66", :type => "Fixnum"}) + {:name => "@a", :value => "66", :type => int_type_name}) send_cont end @@ -179,7 +179,7 @@ def test_non_string_from_to_s create_socket ["class BugExample; def to_s; 1; end; end", "b = BugExample.new", "sleep 0.01"] run_to_line(3) send_ruby("v local") - assert_variables(read_variables, 1, {:value => "ERROR: BugExample.to_s method returns Fixnum. Should return String."}) + assert_variables(read_variables, 1, {:value => "ERROR: BugExample.to_s method returns #{int_type_name}. Should return String."}) send_cont end @@ -261,5 +261,11 @@ def assert_variables(vars, count, *expected) end end + private + + def int_type_name + (Fixnum || Integer).name + end + end From 4c8ec4a0a4469fdb900469b2e9f57a2ed0034d0d Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Tue, 12 Dec 2017 00:07:08 +0300 Subject: [PATCH 089/104] add travis configuration --- .travis.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d16b673 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +language: ruby +dist: trusty +os: + - linux + - osx + +rvm: + - 1.9.3 + - 2.0.0 + - 2.1.10 + - 2.2.8 + - 2.3.5 + - 2.4.2 + - 2.5.0-preview1 + - ruby-head + +matrix: + fast_finish: true + allow_failures: + - rvm: ruby-head From 44e88a2d366608c6271d541d087cc9f3b8a6fd86 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Tue, 12 Dec 2017 00:27:46 +0300 Subject: [PATCH 090/104] add travis build badge to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 90df01e..d0522d6 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,4 @@ An interface which glues ruby-debug to IDEs like Eclipse (RDT), NetBeans and Rub [![official JetBrains project](http://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![Gem Version](https://badge.fury.io/rb/ruby-debug-ide.svg)][gem] +[![Build Status](https://travis-ci.org/ruby-debug/ruby-debug-ide.svg?branch=master)](https://travis-ci.org/ruby-debug/ruby-debug-ide) From 771cd3cabbce8b28e0090ebde2486ec8eb8690db Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Tue, 12 Dec 2017 10:37:55 +0300 Subject: [PATCH 091/104] do not even try to include debase if ruby < 2.0 --- Gemfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index c2689bf..c324150 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,9 @@ source "http://rubygems.org/" gem "ruby-debug-base", :platforms => [:jruby, :ruby_18, :mingw_18] gem "ruby-debug-base19x", ">= 0.11.30.pre4", :platforms => [:ruby_19, :mingw_19] -gem "debase", ">= 0.2.2.beta12", :platforms => [:ruby_20, :mingw_20, :ruby_21, :mingw_21, :ruby_22, :mingw_22, :ruby_23, :mingw_23, :ruby_24, :mingw_24] +if RUBY_VERSION && RUBY_VERSION >= "2.0" + gem "debase", ">= 0.2.2.beta12", :platforms => [:ruby_20, :mingw_20, :ruby_21, :mingw_21, :ruby_22, :mingw_22, :ruby_23, :mingw_23, :ruby_24, :mingw_24] +end gemspec From 541dd3be03f1a32ee5ef35e6b4e30379475a51ae Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Tue, 12 Dec 2017 10:58:48 +0300 Subject: [PATCH 092/104] refactor platforms specification and add mri_25 for debase --- Gemfile | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index c324150..1ec84a9 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,19 @@ source "http://rubygems.org/" -gem "ruby-debug-base", :platforms => [:jruby, :ruby_18, :mingw_18] -gem "ruby-debug-base19x", ">= 0.11.30.pre4", :platforms => [:ruby_19, :mingw_19] +# @param [Array] versions compatible ruby versions +# @return [Array] an array with mri platforms of given versions +def mries(*versions) + versions.flat_map do |v| + %w(ruby mingw x64_mingw) + .map { |platform| "#{platform}_#{v}".to_sym unless platform == "x64_mingw" && v < "2.0" } + .delete_if &:nil? + end +end + +gem "ruby-debug-base", :platforms => [:jruby, *mries('18')] +gem "ruby-debug-base19x", ">= 0.11.30.pre4", :platforms => mries('19') if RUBY_VERSION && RUBY_VERSION >= "2.0" - gem "debase", ">= 0.2.2.beta12", :platforms => [:ruby_20, :mingw_20, :ruby_21, :mingw_21, :ruby_22, :mingw_22, :ruby_23, :mingw_23, :ruby_24, :mingw_24] + gem "debase", ">= 0.2.2.beta12", :platforms => mries('20', '21', '22', '23', '24', '25') end gemspec From 179ff307755f358b8f0bc256148c3fa8faa87d67 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Tue, 12 Dec 2017 11:09:29 +0300 Subject: [PATCH 093/104] try to install 1.9.3 and 2.0.0 from sources on agents --- .travis.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.travis.yml b/.travis.yml index d16b673..3bb50dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,3 +18,17 @@ matrix: fast_finish: true allow_failures: - rvm: ruby-head + + exclude: + - os: osx + rvm: 1.9.3 + - os: osx + rvm: 2.0.0 + + include: + - os: osx + rvm: 1.9.3 + before_script: rvm install ruby-1.9.3 # not binary + - os: osx + rvm: 2.0.0 + before_script: rvm install ruby-2.0.0 # not binary \ No newline at end of file From 35695e006c7f0a52a8738e97f6d49ff8f8e473dc Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Tue, 12 Dec 2017 19:04:09 +0300 Subject: [PATCH 094/104] bump ruby-debug-base19 dep version to 0.11.32 This fixes skipped stepping on oneliner blocks --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 1ec84a9..eff0f6c 100644 --- a/Gemfile +++ b/Gemfile @@ -11,7 +11,7 @@ def mries(*versions) end gem "ruby-debug-base", :platforms => [:jruby, *mries('18')] -gem "ruby-debug-base19x", ">= 0.11.30.pre4", :platforms => mries('19') +gem "ruby-debug-base19x", ">= 0.11.32", :platforms => mries('19') if RUBY_VERSION && RUBY_VERSION >= "2.0" gem "debase", ">= 0.2.2.beta12", :platforms => mries('20', '21', '22', '23', '24', '25') end From d40d44679e917145660a19f65635b09f5257079f Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Tue, 12 Dec 2017 19:06:03 +0300 Subject: [PATCH 095/104] exclude some more versions which just travis does not support --- .travis.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3bb50dc..40f8f23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,11 +24,13 @@ matrix: rvm: 1.9.3 - os: osx rvm: 2.0.0 + - os: linux + rvm: 2.5.0-preview1 - include: - - os: osx - rvm: 1.9.3 - before_script: rvm install ruby-1.9.3 # not binary - - os: osx - rvm: 2.0.0 - before_script: rvm install ruby-2.0.0 # not binary \ No newline at end of file +# include: +# - os: osx +# rvm: 1.9.3 +# before_script: rvm install ruby-1.9.3 # not binary +# - os: osx +# rvm: 2.0.0 +# before_script: rvm install ruby-2.0.0 # not binary \ No newline at end of file From a1562a16e06b389d0f9ab5125180560962cd101d Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Tue, 12 Dec 2017 19:46:54 +0300 Subject: [PATCH 096/104] teardown timeout increased --- test-base/test_base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-base/test_base.rb b/test-base/test_base.rb index afc3ab3..5fc3523 100644 --- a/test-base/test_base.rb +++ b/test-base/test_base.rb @@ -57,7 +57,7 @@ def teardown send_ruby("cont") end debug "Waiting for the server process to finish..." - (config_load('server_start_up_timeout')*4).times do + (config_load('server_start_up_timeout')*5).times do unless @process_finished debug '.' sleep 0.25 From 1280fd49ac75b269708b4fc2b6cb74847242c3a3 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Tue, 12 Dec 2017 20:41:05 +0300 Subject: [PATCH 097/104] added one more space at the end of the string to parse --- lib/ruby-debug-ide/commands/expression_info.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/commands/expression_info.rb b/lib/ruby-debug-ide/commands/expression_info.rb index a03bc83..3eccd52 100644 --- a/lib/ruby-debug-ide/commands/expression_info.rb +++ b/lib/ruby-debug-ide/commands/expression_info.rb @@ -9,7 +9,7 @@ def regexp end def execute - string_to_parse = Command.unescape_incoming(@match.post_match) + " \n\n\n" + string_to_parse = Command.unescape_incoming(@match.post_match) + " \n \n\n" total_lines = string_to_parse.count("\n") + 1 lexer = RubyLex.new From b8c820fbc687a7d885a01c935f8669ba22c11cd9 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Thu, 14 Dec 2017 17:58:51 +0300 Subject: [PATCH 098/104] `@test2_path` force encoding to utf-8 --- test-base/test_base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-base/test_base.rb b/test-base/test_base.rb index 5fc3523..1caf216 100644 --- a/test-base/test_base.rb +++ b/test-base/test_base.rb @@ -141,7 +141,7 @@ def create_file(script_name, lines) def create_test2(lines) @test2_name = "test2.rb" - @test2_path = create_file(@test2_name, lines) + @test2_path = create_file(@test2_name, lines).force_encoding(Encoding::UTF_8) end # Creates test.rb with the given lines, set up @test_name and @test_path From bcaae13730a9b899db09b0e53f623e9212d95340 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Mon, 18 Dec 2017 19:44:29 +0300 Subject: [PATCH 099/104] realpath in file creation added --- test-base/test_base.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test-base/test_base.rb b/test-base/test_base.rb index 1caf216..01bd85d 100644 --- a/test-base/test_base.rb +++ b/test-base/test_base.rb @@ -132,7 +132,8 @@ def TestBase.find_free_port(port = 1098) end def create_file(script_name, lines) - script_path = File.join(TMP_DIR, script_name) + script_path = File.realdirpath(File.join(TMP_DIR, script_name)) + File.open(script_path, "w") do |script| script.printf(lines.join("\n")) end From b91656113887c8604e4ed649791a6f689177a2f6 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Mon, 18 Dec 2017 19:57:07 +0300 Subject: [PATCH 100/104] inc debase version in gemfile --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index eff0f6c..2f97c35 100644 --- a/Gemfile +++ b/Gemfile @@ -13,7 +13,7 @@ end gem "ruby-debug-base", :platforms => [:jruby, *mries('18')] gem "ruby-debug-base19x", ">= 0.11.32", :platforms => mries('19') if RUBY_VERSION && RUBY_VERSION >= "2.0" - gem "debase", ">= 0.2.2.beta12", :platforms => mries('20', '21', '22', '23', '24', '25') + gem "debase", ">= 0.2.2.beta14", :platforms => mries('20', '21', '22', '23', '24', '25') end gemspec From 6f7703fa561df689c9739b4df9ccbdd367dc1c67 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Wed, 20 Dec 2017 14:39:50 +0300 Subject: [PATCH 101/104] bump version --- lib/ruby-debug-ide/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index 3287e97..908fcb2 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1.beta11' + IDE_VERSION='0.6.1.beta12' end From f9a0efd5bc888b2ab5099acbb7803a0473d81a00 Mon Sep 17 00:00:00 2001 From: Viuginov Nickolay Date: Wed, 6 Dec 2017 19:53:34 +0300 Subject: [PATCH 102/104] deleted check instance has `object_id` method --- lib/ruby-debug-ide/xml_printer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index de425f5..49b0b2e 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -279,7 +279,7 @@ def print_variable(name, value, kind) print("", CGI.escapeHTML(name), build_compact_value_attr(value, value_str), kind, build_value_attr(escaped_value_str), value.class, - has_children, value.respond_to?(:object_id) ? value.object_id : value.id) + has_children, value.object_id) print("", escaped_value_str) if Debugger.value_as_nested_element print('') rescue StandardError => e From d81b309a7c951465e9d27d95bd1eeac762bd3425 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Wed, 31 Jan 2018 21:39:44 +0300 Subject: [PATCH 103/104] bump version to 0.6.1 (stable) debase dependency changed to semantic on stable 0.2.2 --- Gemfile | 2 +- lib/ruby-debug-ide/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 2f97c35..9d8626c 100644 --- a/Gemfile +++ b/Gemfile @@ -13,7 +13,7 @@ end gem "ruby-debug-base", :platforms => [:jruby, *mries('18')] gem "ruby-debug-base19x", ">= 0.11.32", :platforms => mries('19') if RUBY_VERSION && RUBY_VERSION >= "2.0" - gem "debase", ">= 0.2.2.beta14", :platforms => mries('20', '21', '22', '23', '24', '25') + gem "debase", "~> 0.2.2", :platforms => mries('20', '21', '22', '23', '24', '25') end gemspec diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index 908fcb2..41f8b9f 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1.beta12' + IDE_VERSION='0.6.1' end From e0c1bb2f1dbe878f779df6b7d2f7413b43c1b145 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Wed, 31 Jan 2018 21:40:42 +0300 Subject: [PATCH 104/104] add 2.5.0 to travis ruby versions --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 40f8f23..31827b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ rvm: - 2.2.8 - 2.3.5 - 2.4.2 - - 2.5.0-preview1 + - 2.5.0 - ruby-head matrix: @@ -24,8 +24,6 @@ matrix: rvm: 1.9.3 - os: osx rvm: 2.0.0 - - os: linux - rvm: 2.5.0-preview1 # include: # - os: osx