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_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
CONFIG_MAP =
CONFIG_SET.map{|k, (ev, _)| [k, ev]}.to_h.freeze
CONFIG =
VERSION =
"1.9.2"
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



2187
2188
2189
# 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) ⇒ Object

manual configuration methods



2183
2184
2185
# 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) ⇒ Object

Unix domain socket configuration



463
464
465
466
467
468
469
470
471
472
473
474
475
# 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) ⇒ Object



2387
2388
2389
2390
2391
# 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

.commandsObject



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

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.



2433
2434
2435
# File 'lib/debug/session.rb', line 2433

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

.create_unix_domain_socket_name(base_dir = unix_domain_socket_dir) ⇒ Object



521
522
523
524
525
526
# 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) ⇒ Object



517
518
519
# 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) ⇒ Object



2393
2394
2395
2396
2397
# File 'lib/debug/session.rb', line 2393

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

.helpObject



574
575
576
577
578
579
580
581
582
583
584
585
# 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

.helpsObject



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

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

.info(msg) ⇒ Object



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

def self.info msg
  log :INFO, msg
end

.load_rcObject



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



2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
# 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) ⇒ Object



2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
# 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) ⇒ Object



2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
# 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) ⇒ Object



2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
# 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_helpObject

Help



530
531
532
533
534
535
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
# 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_locationObject

String for requiring location nil for -r



2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
# 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) ⇒ Object



2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
# 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_suspendObject

boot utilities



2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
# 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?Boolean

Returns:

  • (Boolean)


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

def skip?
  @skip_all
end

.skip_allObject



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

def skip_all
  @skip_all = true
end

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

start methods



2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
# 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) ⇒ Object



2420
2421
2422
2423
2424
2425
2426
# 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_dirObject



504
505
506
507
508
509
510
511
512
513
514
515
# 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_homedirObject



492
493
494
495
496
497
498
499
500
501
502
# 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_tmpdirObject



477
478
479
480
481
482
483
484
485
486
487
488
489
490
# 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) ⇒ Object



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

def self.warn msg
  log :WARN, msg
end