123456789_123456789_123456789_123456789_123456789_

Module: DEBUGGER__

Overview

$VERBOSE = true

Constant Summary

  • CONFIG =
    # File 'lib/debug/config.rb', line 459
    
      
  • CONFIG_MAP =
    # File 'lib/debug/config.rb', line 60
    CONFIG_SET.map{|k, (ev, _)| [k, ev]}.to_h.freeze
  • CONFIG_SET =
    # File 'lib/debug/config.rb', line 13
    {
      # UI setting
      log_level:      ['RUBY_DEBUG_LOG_LEVEL',      "UI: Log level same as Logger",               :loglevel, "WARN"],
      show_src_lines: ['RUBY_DEBUG_SHOW_SRC_LINES', "UI: Show n lines source code on breakpoint", :int, "10"],
      show_evaledsrc: ['RUBY_DEBUG_SHOW_EVALEDSRC', "UI: Show actually evaluated source",         :bool, "false"],
      show_frames:    ['RUBY_DEBUG_SHOW_FRAMES',    "UI: Show n frames on breakpoint",            :int, "2"],
      use_short_path: ['RUBY_DEBUG_USE_SHORT_PATH', "UI: Show shorten PATH (like $(Gem)/foo.rb)", :bool, "false"],
      no_color:       ['RUBY_DEBUG_NO_COLOR',       "UI: Do not use colorize",                    :bool, "false"],
      no_sigint_hook: ['RUBY_DEBUG_NO_SIGINT_HOOK', "UI: Do not suspend on SIGINT",               :bool, "false"],
      no_reline:      ['RUBY_DEBUG_NO_RELINE',      "UI: Do not use Reline library",              :bool, "false"],
      no_hint:        ['RUBY_DEBUG_NO_HINT',        "UI: Do not show the hint on the REPL",       :bool, "false"],
      no_lineno:      ['RUBY_DEBUG_NO_LINENO',      "UI: Do not show line numbers",               :bool, "false"],
      irb_console:    ["RUBY_DEBUG_IRB_CONSOLE",    "UI: Use IRB as the console",                 :bool, "false"],
    
      # control setting
      skip_path:      ['RUBY_DEBUG_SKIP_PATH',      "CONTROL: Skip showing/entering frames for given paths", :path],
      skip_nosrc:     ['RUBY_DEBUG_SKIP_NOSRC',     "CONTROL: Skip on no source code lines",              :bool, "false"],
      keep_alloc_site:['RUBY_DEBUG_KEEP_ALLOC_SITE',"CONTROL: Keep allocation site and p, pp shows it",   :bool, "false"],
      postmortem:     ['RUBY_DEBUG_POSTMORTEM',     "CONTROL: Enable postmortem debug",                   :bool, "false"],
      fork_mode:      ['RUBY_DEBUG_FORK_MODE',      "CONTROL: Control which process activates a debugger after fork (both/parent/child)", :forkmode, "both"],
      sigdump_sig:    ['RUBY_DEBUG_SIGDUMP_SIG',    "CONTROL: Sigdump signal", :bool, "false"],
    
      # boot setting
      nonstop:        ['RUBY_DEBUG_NONSTOP',     "BOOT: Nonstop mode",                                                :bool, "false"],
      stop_at_load:   ['RUBY_DEBUG_STOP_AT_LOAD',"BOOT: Stop at just loading location",                               :bool, "false"],
      init_script:    ['RUBY_DEBUG_INIT_SCRIPT', "BOOT: debug command script path loaded at first stop"],
      commands:       ['RUBY_DEBUG_COMMANDS',    "BOOT: debug commands invoked at first stop. Commands should be separated by `;;`"],
      no_rc:          ['RUBY_DEBUG_NO_RC',       "BOOT: ignore loading ~/.rdbgrc(.rb)",                               :bool, "false"],
      history_file:   ['RUBY_DEBUG_HISTORY_FILE',"BOOT: history file",               :string, "~/.rdbg_history"],
      save_history:   ['RUBY_DEBUG_SAVE_HISTORY',"BOOT: maximum save history lines", :int, "10000"],
    
      # remote setting
      open:           ['RUBY_DEBUG_OPEN',         "REMOTE: Open remote port (same as `rdbg --open` option)"],
      port:           ['RUBY_DEBUG_PORT',         "REMOTE: TCP/IP remote debugging: port"],
      host:           ['RUBY_DEBUG_HOST',         "REMOTE: TCP/IP remote debugging: host", :string, "127.0.0.1"],
      sock_path:      ['RUBY_DEBUG_SOCK_PATH',    "REMOTE: UNIX Domain Socket remote debugging: socket path"],
      sock_dir:       ['RUBY_DEBUG_SOCK_DIR',     "REMOTE: UNIX Domain Socket remote debugging: socket directory"],
      local_fs_map:   ['RUBY_DEBUG_LOCAL_FS_MAP', "REMOTE: Specify local fs map", :path_map],
      skip_bp:        ['RUBY_DEBUG_SKIP_BP',      "REMOTE: Skip breakpoints if no clients are attached", :bool, 'false'],
      cookie:         ['RUBY_DEBUG_COOKIE',       "REMOTE: Cookie for negotiation"],
      session_name:   ['RUBY_DEBUG_SESSION_NAME', "REMOTE: Session name for differentiating multiple sessions"],
      chrome_path:    ['RUBY_DEBUG_CHROME_PATH',  "REMOTE: Platform dependent path of Chrome (For more information, See [here](https://github.com/ruby/debug/pull/334/files#diff-5fc3d0a901379a95bc111b86cf0090b03f857edfd0b99a0c1537e26735698453R55-R64))"],
    
      # obsolete
      parent_on_fork: ['RUBY_DEBUG_PARENT_ON_FORK', "OBSOLETE: Keep debugging parent process on fork",     :bool, "false"],
    }.freeze
  • LOG_LEVELS =
    # File 'lib/debug/config.rb', line 4
    {
      UNKNOWN: 0,
      FATAL:   1,
      ERROR:   2,
      WARN:    3,
      INFO:    4,
      DEBUG:   5
    }.freeze
  • M_CLASS =
    # File 'lib/debug/thread_client.rb', line 15
    method(:class).unbind
  • M_INSTANCE_VARIABLES =
    # File 'lib/debug/thread_client.rb', line 13
    method(:instance_variables).unbind
  • M_INSTANCE_VARIABLE_GET =
    # File 'lib/debug/thread_client.rb', line 14
    method(:instance_variable_get).unbind
  • M_KIND_OF_P =
    # File 'lib/debug/thread_client.rb', line 17
    method(:kind_of?).unbind
  • M_METHOD =
    # File 'lib/debug/thread_client.rb', line 19
    method(:method).unbind
  • M_NAME =
    # File 'lib/debug/thread_client.rb', line 21
    method(:name).unbind
  • M_OBJECT_ID =
    # File 'lib/debug/thread_client.rb', line 20
    method(:object_id).unbind
  • M_RESPOND_TO_P =
    # File 'lib/debug/thread_client.rb', line 18
    method(:respond_to?).unbind
  • M_SINGLETON_CLASS =
    # File 'lib/debug/thread_client.rb', line 16
    method(:singleton_class).unbind
  • SHORT_INSPECT_LENGTH =

    Inspector

    # File 'lib/debug/session.rb', line 2332
    40
  • VERSION =
    # File 'lib/debug/version.rb', line 4
    "1.9.2"

Class Attribute Summary

Class Method Summary

Class Attribute Details

.skip?Boolean (readonly)

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2298

def skip?
  @skip_all
end

Class Method Details

.add_catch_breakpoint(pat)

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2187

def self.add_catch_breakpoint pat
  ::DEBUGGER__::SESSION.add_catch_breakpoint pat
end

.add_line_breakpoint(file, line, **kw)

manual configuration methods

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2183

def self.add_line_breakpoint file, line, **kw
  ::DEBUGGER__::SESSION.add_line_breakpoint file, line, **kw
end

.check_dir_authority(path)

Unix domain socket configuration

[ GitHub ]

  
# File 'lib/debug/config.rb', line 463

def self.check_dir_authority path
  fs = File.stat(path)

  unless (dir_uid = fs.uid) == (uid = Process.uid)
    raise "#{path} uid is #{dir_uid}, but Process.uid is #{uid}"
  end

  if fs.world_writable? && !fs.sticky?
    raise "#{path} is world writable but not sticky"
  end

  path
end

.check_loglevel(level)

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2387

def self.check_loglevel level
  lv = LOG_LEVELS[level]
  config_lv = LOG_LEVELS[CONFIG[:log_level]]
  lv <= config_lv
end

.commands

[ GitHub ]

  
# File 'lib/debug/config.rb', line 570

def self.commands
  (defined?(@commands) && @commands) || (parse_help; @commands)
end

.compare_path(a, b)

For case insensitive file system (like Windows) Note that this check is not enough because case sensitive/insensitive is depend on the file system. So this check is only roughly estimation.

See additional method definition at line 2433.

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2437

def self.compare_path(a, b)
  a&.downcase == b&.downcase
end

.create_unix_domain_socket_name(base_dir = unix_domain_socket_dir)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 521

def self.create_unix_domain_socket_name(base_dir = unix_domain_socket_dir)
  suffix = "-#{Process.pid}"
  name = CONFIG[:session_name]
  suffix << "-#{name}" if name
  create_unix_domain_socket_name_prefix(base_dir) + suffix
end

.create_unix_domain_socket_name_prefix(base_dir = unix_domain_socket_dir)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 517

def self.create_unix_domain_socket_name_prefix(base_dir = unix_domain_socket_dir)
  File.join(base_dir, "rdbg")
end

.debug(&b)

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2393

def self.debug(&b)
  if check_loglevel :DEBUG
    log :DEBUG, b.call
  end
end

.help

[ GitHub ]

  
# File 'lib/debug/config.rb', line 574

def self.help
  r = []
  self.helps.each{|cat, cmds|
    r << "### #{cat}"
    r << ''
    cmds.each{|_, desc|
      r << desc
    }
    r << ''
  }
  r.join("\n")
end

.helps

[ GitHub ]

  
# File 'lib/debug/config.rb', line 566

def self.helps
  (defined?(@helps) && @helps) || parse_help
end

.info(msg)

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2383

def self.info msg
  log :INFO, msg
end

.load_rc

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2303

def self.load_rc
  [[File.expand_path('~/.rdbgrc'), true],
   [File.expand_path('~/.rdbgrc.rb'), true],
   # ['./.rdbgrc', true], # disable because of security concern
   [CONFIG[:init_script], false],
   ].each{|(path, rc)|
    next unless path
    next if rc && CONFIG[:no_rc] # ignore rc

    if File.file? path
      if path.end_with?('.rb')
        load path
      else
        ::DEBUGGER__::SESSION.add_preset_commands path, File.readlines(path)
      end
    elsif !rc
      warn "Not found: #{path}"
    end
  }

  # given debug commands
  if CONFIG[:commands]
    cmds = CONFIG[:commands].split(';;')
    ::DEBUGGER__::SESSION.add_preset_commands "commands", cmds, kick: false, continue: false
  end
end

.log(level, msg)

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2399

def self.log level, msg
  if check_loglevel level
    @logfile = STDERR unless defined? @logfile
    return if @logfile.closed?

    if defined? SESSION
      pi = SESSION.process_info
      process_info = pi ? "[#{pi}]" : nil
    end

    if level == :WARN
      # :WARN on debugger is general information
      @logfile.puts "DEBUGGER#{process_info}: #{msg}"
      @logfile.flush
    else
      @logfile.puts "DEBUGGER#{process_info} (#{level}): #{msg}"
      @logfile.flush
    end
  end
end

.open(host: nil, port: , sock_path: nil, sock_dir: nil, nonstop: false, **kw)

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2225

def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
  CONFIG.set_config(**kw)
  require_relative 'server'

  if port || CONFIG[:open] == 'chrome' || (!::Addrinfo.respond_to?(:unix))
    open_tcp host: host, port: (port || 0), nonstop: nonstop
  else
    open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
  end
end

.open_tcp(host: nil, port:, nonstop: false, **kw)

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2236

def self.open_tcp host: nil, port:, nonstop: false, **kw
  CONFIG.set_config(**kw)
  require_relative 'server'

  if defined? SESSION
    SESSION.reset_ui UI_TcpServer.new(host: host, port: port)
  else
    initialize_session{ UI_TcpServer.new(host: host, port: port) }
  end

  setup_initial_suspend unless nonstop
end

.open_unix(sock_path: nil, sock_dir: nil, nonstop: false, **kw)

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2249

def self.open_unix sock_path: nil, sock_dir: nil, nonstop: false, **kw
  CONFIG.set_config(**kw)
  require_relative 'server'

  if defined? SESSION
    SESSION.reset_ui UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
  else
    initialize_session{ UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path) }
  end

  setup_initial_suspend unless nonstop
end

.parse_help

Help

[ GitHub ]

  
# File 'lib/debug/config.rb', line 530

def self.parse_help
  helps = Hash.new{|h, k| h[k] = []}
  desc = cat = nil
  cmds = Hash.new

  File.read(File.join(__dir__, 'session.rb'), encoding: Encoding::UTF_8).each_line do |line|
    case line
    when /\A\s*### (.+)/
      cat = $1
      break if $1 == 'END'
    when /\A      register_command (.+)/
      next unless cat
      next unless desc

      ws = []
      $1.gsub(/'([a-z]+)'/){|w|
        ws << $1
      }
      helps[cat] << [ws, desc]
      desc = nil
      max_w = ws.max_by{|w| w.length}
      ws.each{|w|
        cmds[w] = max_w
      }
    when /\A\s+# (\s*\*.+)/
      if desc
        desc << "\n" + $1
      else
        desc = $1
      end
    end
  end
  @commands = cmds
  @helps = helps
end

.require_location

String for requiring location nil for -r

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2193

def self.require_location
  locs = caller_locations
  dir_prefix = /#{Regexp.escape(__dir__)}/

  locs.each do |loc|
    case loc.absolute_path
    when dir_prefix
    when %r{rubygems/core_ext/kernel_require\.rb}
    when %r{bundled_gems\.rb}
    else
      return loc if loc.absolute_path
    end
  end
  nil
end

.safe_inspect(obj, max_length: SHORT_INSPECT_LENGTH, short: false)

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2361

def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
  if short
    LimitedPP.pp(obj, max_length)
  else
    obj.inspect
  end
rescue NoMethodError => e
  klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
  if obj == (r = e.receiver)
    "<\##{klass.name}#{oid} does not have \#inspect>"
  else
    rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
    "<\##{klass.name}:#{roid} contains <\##{rklass}:#{roid} and it does not have #inspect>"
  end
rescue Exception => e
  "<#inspect raises #{e.inspect}>"
end

.setup_initial_suspend

boot utilities

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2264

def self.setup_initial_suspend
  if !CONFIG[:nonstop]
    case
    when CONFIG[:stop_at_load]
      add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, hook_call: false
      nil # stop here
    when path = ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH']
      add_line_breakpoint path, 0, oneshot: true, hook_call: false
    when loc = ::DEBUGGER__.require_location
      # require 'debug/start' or 'debug'
      add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
    else
      # -r
      add_line_breakpoint $0, 0, oneshot: true, hook_call: false
    end
  end
end

.skip_all

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2294

def skip_all
  @skip_all = true
end

.start(nonstop: false, **kw)

start methods

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2211

def self.start nonstop: false, **kw
  CONFIG.set_config(**kw)

  if CONFIG[:open]
    open nonstop: nonstop, **kw
  else
    unless defined? SESSION
      require_relative 'local'
      initialize_session{ UI_LocalConsole.new }
    end
    setup_initial_suspend unless nonstop
  end
end

.step_in(&b)

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2420

def self.step_in &b
  if defined?(SESSION) && SESSION.active?
    SESSION.add_iseq_breakpoint RubyVM::InstructionSequence.of(b), oneshot: true
  end

  yield
end

.unix_domain_socket_dir

[ GitHub ]

  
# File 'lib/debug/config.rb', line 504

def self.unix_domain_socket_dir
  case
  when path = CONFIG[:sock_dir]
  when path = ENV['XDG_RUNTIME_DIR']
  when path = unix_domain_socket_tmpdir
  when path = unix_domain_socket_homedir
  else
    raise 'specify RUBY_DEBUG_SOCK_DIR environment variable.'
  end

  path
end

.unix_domain_socket_homedir

[ GitHub ]

  
# File 'lib/debug/config.rb', line 492

def self.unix_domain_socket_homedir
  if home = ENV['HOME']
    path = File.join(home, '.rdbg-sock')

    unless File.exist?(path)
      Dir.mkdir(path, 0700)
    end

    check_dir_authority(path)
  end
end

.unix_domain_socket_tmpdir

[ GitHub ]

  
# File 'lib/debug/config.rb', line 477

def self.unix_domain_socket_tmpdir
  require 'tmpdir'

  if tmpdir = Dir.tmpdir
    path = File.join(tmpdir, "rdbg-#{Process.uid}")

    unless File.exist?(path)
      d = Dir.mktmpdir
      File.rename(d, path)
    end

    check_dir_authority(path)
  end
end

.warn(msg)

[ GitHub ]

  
# File 'lib/debug/session.rb', line 2379

def self.warn msg
  log :WARN, msg
end