Module: DEBUGGER__

Defined in:
lib/debug/client.rb,
lib/debug/color.rb,
lib/debug/local.rb,
lib/debug/config.rb,
lib/debug/server.rb,
lib/debug/tracer.rb,
lib/debug/console.rb,
lib/debug/version.rb,
lib/debug/breakpoint.rb,
lib/debug/frame_info.rb,
lib/debug/server_cdp.rb,
lib/debug/server_dap.rb,
lib/debug/thread_client.rb,
lib/debug/abbrev_command.rb,
lib/debug/irb_integration.rb,
lib/debug/source_repository.rb,
lib/debug/dap_custom/traceInspector.rb,
lib/debug/session.rb

Overview

$VERBOSE = true

Defined Under Namespace

Modules: Color, DAP_TraceInspector, ForkInterceptor, GlobalVariablesHelper, IrbPatch, MultiProcessGroup, SkipPathHelper, TrapInterceptor, UI_CDP, UI_DAP Classes: AbbrevCommand, Breakpoint, CallTracer, CatchBreakpoint, CheckBreakpoint, Client, CommandLineOptionError, Config, Console, ExceptionTracer, FrameInfo, ISeqBreakpoint, LimitedPP, LineBreakpoint, LineTracer, MethodBreakpoint, NaiveString, ObjectTracer, PostmortemError, PresetCommands, ProcessGroup, Session, SessionCommand, SourceRepository, ThreadClient, Tracer, UI_Base, UI_LocalConsole, UI_ServerBase, UI_TcpServer, UI_UnixDomainServer, WatchIVarBreakpoint

Constant Summary collapse

LOG_LEVELS =
{
  UNKNOWN: 0,
  FATAL:   1,
  ERROR:   2,
  WARN:    3,
  INFO:    4,
  DEBUG:   5
}.freeze
CONFIG_SET =
{
  # 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_src_lines_frame:['RUBY_DEBUG_SHOW_SRC_LINES_FRAME', "UI: Show n lines source code on frame operations", :int, "1"],
  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"],
  no_repeat:           ['RUBY_DEBUG_NO_REPEAT',      "UI: Do not repeat last line when empty line",: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 (default: ${XDG_STATE_HOME-~/.local/state}/rdbg/history)",        :string, nil],
  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"],
  port_range:          ['RUBY_DEBUG_PORT_RANGE',   "REMOTE: TCP/IP remote debugging: length of port range"],
  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
CONFIG_MAP =
CONFIG_SET.map{|k, (ev, _)| [k, ev]}.to_h.freeze
CONFIG =
VERSION =
"1.11.0"
M_INSTANCE_VARIABLES =
method(:instance_variables).unbind
M_INSTANCE_VARIABLE_GET =
method(:instance_variable_get).unbind
M_CLASS =
method(:class).unbind
M_SINGLETON_CLASS =
method(:singleton_class).unbind
M_KIND_OF_P =
method(:kind_of?).unbind
M_RESPOND_TO_P =
method(:respond_to?).unbind
M_METHOD =
method(:method).unbind
M_OBJECT_ID =
method(:object_id).unbind
M_NAME =
method(:name).unbind
SHORT_INSPECT_LENGTH =

Inspector

40

Class Method Summary collapse

Class Method Details

.add_catch_breakpoint(pat) ⇒ Object



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

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

.add_line_breakpoint(file, line, **kw) ⇒ Object

manual configuration methods



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

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

.check_dir_authority(path) ⇒ Object

Unix domain socket configuration



469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/debug/config.rb', line 469

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) ⇒ Object



2395
2396
2397
2398
2399
# File 'lib/debug/session.rb', line 2395

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

.commandsObject



576
577
578
# File 'lib/debug/config.rb', line 576

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

.compare_path(a, b) ⇒ Object

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.



2441
2442
2443
# File 'lib/debug/session.rb', line 2441

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

.create_unix_domain_socket_name(base_dir = unix_domain_socket_dir) ⇒ Object



527
528
529
530
531
532
# File 'lib/debug/config.rb', line 527

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) ⇒ Object



523
524
525
# File 'lib/debug/config.rb', line 523

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

.debug(&b) ⇒ Object



2401
2402
2403
2404
2405
# File 'lib/debug/session.rb', line 2401

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

.helpObject



580
581
582
583
584
585
586
587
588
589
590
591
# File 'lib/debug/config.rb', line 580

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

.helpsObject



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

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

.info(msg) ⇒ Object



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

def self.info msg
  log :INFO, msg
end

.load_rcObject



2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
# File 'lib/debug/session.rb', line 2302

def self.load_rc
  rc_file_paths = [
    [File.expand_path('~/.rdbgrc'), true],
    [File.expand_path('~/.rdbgrc.rb'), true],
    # ['./.rdbgrc', true], # disable because of security concern
  ]

  if (xdg_home = ENV["XDG_CONFIG_HOME"])
    rc_file_paths << [File.expand_path(File.join(xdg_home, "rdbg", "config")), true]
    rc_file_paths << [File.expand_path(File.join(xdg_home, "rdbg", "config.rb")), true]
  end

  rc_file_paths << [CONFIG[:init_script], false]

  rc_file_paths.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) ⇒ Object



2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
# File 'lib/debug/session.rb', line 2407

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) ⇒ Object



2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
# File 'lib/debug/session.rb', line 2224

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) ⇒ Object



2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
# File 'lib/debug/session.rb', line 2235

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) ⇒ Object



2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
# File 'lib/debug/session.rb', line 2248

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_helpObject

Help



536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
# File 'lib/debug/config.rb', line 536

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_locationObject

String for requiring location nil for -r



2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
# File 'lib/debug/session.rb', line 2192

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) ⇒ Object



2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
# File 'lib/debug/session.rb', line 2369

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_suspendObject

boot utilities



2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
# File 'lib/debug/session.rb', line 2263

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?Boolean

Returns:

  • (Boolean)


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

def skip?
  @skip_all
end

.skip_allObject



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

def skip_all
  @skip_all = true
end

.start(nonstop: false, **kw) ⇒ Object

start methods



2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
# File 'lib/debug/session.rb', line 2210

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) ⇒ Object



2428
2429
2430
2431
2432
2433
2434
# File 'lib/debug/session.rb', line 2428

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_dirObject



510
511
512
513
514
515
516
517
518
519
520
521
# File 'lib/debug/config.rb', line 510

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_homedirObject



498
499
500
501
502
503
504
505
506
507
508
# File 'lib/debug/config.rb', line 498

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_tmpdirObject



483
484
485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/debug/config.rb', line 483

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) ⇒ Object



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

def self.warn msg
  log :WARN, msg
end