def register_default_command
def register_default_command
### Control flow
# * `s[tep]`
# * Step in. Resume the program until next breakable point.
# * `s[tep] <n>`
# * Step in, resume the program at `<n>`th breakable point.
register_command 's', 'step',
repeat: true,
cancel_auto_continue: true,
postmortem: false do |arg|
step_command :in, arg
end
# * `n[ext]`
# * Step over. Resume the program until next line.
# * `n[ext] <n>`
# * Step over, same as `step <n>`.
register_command 'n', 'next',
repeat: true,
cancel_auto_continue: true,
postmortem: false do |arg|
step_command :next, arg
end
# * `fin[ish]`
# * Finish this frame. Resume the program until the current frame is finished.
# * `fin[ish] <n>`
# * Finish `<n>`th frames.
register_command 'fin', 'finish',
repeat: true,
cancel_auto_continue: true,
postmortem: false do |arg|
if arg&.to_i == 0
raise 'finish command with 0 does not make sense.'
end
step_command :finish, arg
end
# * `u[ntil]`
# * Similar to `next` command, but only stop later lines or the end of the current frame.
# * Similar to gdb's `advance` command.
# * `u[ntil] <[file:]line>`
# * Run til the program reaches given location or the end of the current frame.
# * `u[ntil] <name>`
# * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
register_command 'u', 'until',
repeat: true,
cancel_auto_continue: true,
postmortem: false do |arg|
step_command :until, arg
end
# * `c` or `cont` or `continue`
# * Resume the program.
register_command 'c', 'cont', 'continue',
repeat: true,
cancel_auto_continue: true do |arg|
leave_subsession :continue
end
# * `q[uit]` or `Ctrl-D`
# * Finish debugger (with the debuggee process on non-remote debugging).
register_command 'q', 'quit' do |arg|
if ask 'Really quit?'
@ui.quit arg.to_i do
request_tc :quit
end
leave_subsession :continue
else
next :retry
end
end
# * `q[uit]!`
# * Same as q[uit] but without the confirmation prompt.
register_command 'q!', 'quit!', unsafe: false do |arg|
@ui.quit arg.to_i do
request_tc :quit
end
leave_subsession :continue
end
# * `kill`
# * Stop the debuggee process with `Kernel#exit!`.
register_command 'kill' do |arg|
if ask 'Really kill?'
exit! (arg || 1).to_i
else
next :retry
end
end
# * `kill!`
# * Same as kill but without the confirmation prompt.
register_command 'kill!', unsafe: false do |arg|
exit! (arg || 1).to_i
end
# * `sigint`
# * Execute SIGINT handler registered by the debuggee.
# * Note that this command should be used just after stop by `SIGINT`.
register_command 'sigint' do
begin
case cmd = @intercepted_sigint_cmd
when nil, 'IGNORE', :IGNORE, 'DEFAULT', :DEFAULT
# ignore
when String
eval(cmd)
when Proc
cmd.call
end
leave_subsession :continue
rescue Exception => e
@ui.puts "Exception: #{e}"
@ui.puts e.backtrace.map{|line| " #{e}"}
next :retry
end
end
### Breakpoint
# * `b[reak]`
# * Show all breakpoints.
# * `b[reak] <line>`
# * Set breakpoint on `<line>` at the current frame's file.
# * `b[reak] <file>:<line>` or `<file> <line>`
# * Set breakpoint on `<file>:<line>`.
# * `b[reak] <class>#<name>`
# * Set breakpoint on the method `<class>#<name>`.
# * `b[reak] <expr>.<name>`
# * Set breakpoint on the method `<expr>.<name>`.
# * `b[reak] ... if: <expr>`
# * break if `<expr>` is true at specified location.
# * `b[reak] ... pre: <command>`
# * break and run `<command>` before stopping.
# * `b[reak] ... do: <command>`
# * break and run `<command>`, and continue.
# * `b[reak] ... path: <path>`
# * break if the path matches to `<path>`. `<path>` can be a regexp with `/regexp/`.
# * `b[reak] if: <expr>`
# * break if: `<expr>` is true at any lines.
# * Note that this feature is super slow.
register_command 'b', 'break', postmortem: false, unsafe: false do |arg|
if arg == nil
show_bps
next :retry
else
case bp = repl_add_breakpoint(arg)
when :noretry
when nil
next :retry
else
show_bps bp
next :retry
end
end
end
# * `catch <Error>`
# * Set breakpoint on raising `<Error>`.
# * `catch ... if: <expr>`
# * stops only if `<expr>` is true as well.
# * `catch ... pre: <command>`
# * runs `<command>` before stopping.
# * `catch ... do: <command>`
# * stops and run `<command>`, and continue.
# * `catch ... path: <path>`
# * stops if the exception is raised from a `<path>`. `<path>` can be a regexp with `/regexp/`.
register_command 'catch', postmortem: false, unsafe: false do |arg|
if arg
bp = repl_add_catch_breakpoint arg
show_bps bp if bp
else
show_bps
end
:retry
end
# * `watch @ivar`
# * Stop the execution when the result of current scope's `@ivar` is changed.
# * Note that this feature is super slow.
# * `watch ... if: <expr>`
# * stops only if `<expr>` is true as well.
# * `watch ... pre: <command>`
# * runs `<command>` before stopping.
# * `watch ... do: <command>`
# * stops and run `<command>`, and continue.
# * `watch ... path: <path>`
# * stops if the path matches `<path>`. `<path>` can be a regexp with `/regexp/`.
register_command 'wat', 'watch', postmortem: false, unsafe: false do |arg|
if arg && arg.match?(/\A@\w+/)
repl_add_watch_breakpoint(arg)
else
show_bps
:retry
end
end
# * `del[ete]`
# * delete all breakpoints.
# * `del[ete] <bpnum>`
# * delete specified breakpoint.
register_command 'del', 'delete', postmortem: false, unsafe: false do |arg|
case arg
when nil
show_bps
if ask "Remove all breakpoints?", 'N'
delete_bp
end
when /\d+/
bp = delete_bp arg.to_i
else
nil
end
@ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp
:retry
end
### Information
# * `bt` or `backtrace`
# * Show backtrace (frame) information.
# * `bt <num>` or `backtrace <num>`
# * Only shows first `<num>` frames.
# * `bt /regexp/` or `backtrace /regexp/`
# * Only shows frames with method name or location info that matches `/regexp/`.
# * `bt <num> /regexp/` or `backtrace <num> /regexp/`
# * Only shows first `<num>` frames with method name or location info that matches `/regexp/`.
register_command 'bt', 'backtrace', unsafe: false do |arg|
case arg
when /\A(\d+)\z/
request_tc_with_restarted_threads [:show, :backtrace, arg.to_i, nil]
when /\A\/(.*)\/\z/
pattern = $1
request_tc_with_restarted_threads [:show, :backtrace, nil, Regexp.compile(pattern)]
when /\A(\d+)\s+\/(.*)\/\z/
max, pattern = $1, $2
request_tc_with_restarted_threads [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
else
request_tc_with_restarted_threads [:show, :backtrace, nil, nil]
end
end
# * `l[ist]`
# * Show current frame's source code.
# * Next `list` command shows the successor lines.
# * `l[ist] -`
# * Show predecessor lines as opposed to the `list` command.
# * `l[ist] <start>` or `l[ist] <start>-<end>`
# * Show current frame's source code from the line <start> to <end> if given.
register_command 'l', 'list', repeat: true, unsafe: false do |arg|
case arg ? arg.strip : nil
when /\A(\d+)\z/
request_tc [:show, :list, {start_line: arg.to_i - 1}]
when /\A-\z/
request_tc [:show, :list, {dir: -1}]
when /\A(\d+)-(\d+)\z/
request_tc [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
when nil
request_tc [:show, :list]
else
@ui.puts "Can not handle list argument: #{arg}"
:retry
end
end
# * `whereami`
# * Show the current frame with source code.
register_command 'whereami', unsafe: false do
request_tc [:show, :whereami]
end
# * `edit`
# * Open the current file on the editor (use `EDITOR` environment variable).
# * Note that edited file will not be reloaded.
# * `edit <file>`
# * Open <file> on the editor.
register_command 'edit' do |arg|
if @ui.remote?
@ui.puts "not supported on the remote console."
next :retry
end
begin
arg = resolve_path(arg) if arg
rescue Errno::ENOENT
@ui.puts "not found: #{arg}"
next :retry
end
request_tc [:show, :edit, arg]
end
info_subcommands = nil
info_subcommands_abbrev = nil
# * `i[nfo]`
# * Show information about current frame (local/instance variables and defined constants).
# * `i[nfo]` <subcommand>
# * `info` has the following sub-commands.
# * Sub-commands can be specified with few letters which is unambiguous, like `l` for 'locals'.
# * `i[nfo] l or locals or local_variables`
# * Show information about the current frame (local variables)
# * It includes `self` as `%self` and a return value as `_return`.
# * `i[nfo] i or ivars or instance_variables`
# * Show information about instance variables about `self`.
# * `info ivars <expr>` shows the instance variables of the result of `<expr>`.
# * `i[nfo] c or consts or constants`
# * Show information about accessible constants except toplevel constants.
# * `info consts <expr>` shows the constants of a class/module of the result of `<expr>`
# * `i[nfo] g or globals or global_variables`
# * Show information about global variables
# * `i[nfo] th or threads`
# * Show all threads (same as `th[read]`).
# * `i[nfo] b or breakpoints or w or watchpoints`
# * Show all breakpoints and watchpoints.
# * `i[nfo] ... /regexp/`
# * Filter the output with `/regexp/`.
register_command 'i', 'info', unsafe: false do |arg|
if /\/(.+)\/\z/ =~ arg
pat = Regexp.compile($1)
sub = $~.pre_match.strip
else
sub = arg
end
if /\A(.+?)\b(.+)/ =~ sub
sub = $1
opt = $2.strip
opt = nil if opt.empty?
end
if sub && !info_subcommands
info_subcommands = {
locals: %w[ locals local_variables ],
ivars: %w[ ivars instance_variables ],
consts: %w[ consts constants ],
globals:%w[ globals global_variables ],
threads:%w[ threads ],
breaks: %w[ breakpoints ],
watchs: %w[ watchpoints ],
}
require_relative 'abbrev_command'
info_subcommands_abbrev = AbbrevCommand.new(info_subcommands)
end
if sub
sub = info_subcommands_abbrev.search sub, :unknown do |candidates|
# note: unreached now
@ui.puts "Ambiguous command '#{sub}': #{candidates.join(' ')}"
end
end
case sub
when nil
request_tc_with_restarted_threads [:show, :default, pat] # something useful
when :locals
request_tc_with_restarted_threads [:show, :locals, pat]
when :ivars
request_tc_with_restarted_threads [:show, :ivars, pat, opt]
when :consts
request_tc_with_restarted_threads [:show, :consts, pat, opt]
when :globals
request_tc_with_restarted_threads [:show, :globals, pat]
when :threads
thread_list
:retry
when :breaks, :watchs
show_bps
:retry
else
@ui.puts "unrecognized argument for info command: #{arg}"
show_help 'info'
:retry
end
end
# * `o[utline]` or `ls`
# * Show you available methods, constants, local variables, and instance variables in the current scope.
# * `o[utline] <expr>` or `ls <expr>`
# * Show you available methods and instance variables of the given object.
# * If the object is a class/module, it also lists its constants.
register_command 'outline', 'o', 'ls', unsafe: false do |arg|
request_tc_with_restarted_threads [:show, :outline, arg]
end
# * `display`
# * Show display setting.
# * `display <expr>`
# * Show the result of `<expr>` at every suspended timing.
register_command 'display', postmortem: false do |arg|
if arg && !arg.empty?
@displays << arg
request_eval :try_display, @displays
else
request_eval :display, @displays
end
end
# * `undisplay`
# * Remove all display settings.
# * `undisplay <displaynum>`
# * Remove a specified display setting.
register_command 'undisplay', postmortem: false, unsafe: false do |arg|
case arg
when /(\d+)/
if @displays[n = $1.to_i]
@displays.delete_at n
end
request_eval :display, @displays
when nil
if ask "clear all?", 'N'
@displays.clear
end
:retry
end
end
### Frame control
# * `f[rame]`
# * Show the current frame.
# * `f[rame] <framenum>`
# * Specify a current frame. Evaluation are run on specified frame.
register_command 'frame', 'f', unsafe: false do |arg|
request_tc [:frame, :set, arg]
end
# * `up`
# * Specify the upper frame.
register_command 'up', repeat: true, unsafe: false do |arg|
request_tc [:frame, :up]
end
# * `down`
# * Specify the lower frame.
register_command 'down', repeat: true, unsafe: false do |arg|
request_tc [:frame, :down]
end
### Evaluate
# * `p <expr>`
# * Evaluate like `p <expr>` on the current frame.
register_command 'p' do |arg|
request_eval :p, arg.to_s
end
# * `pp <expr>`
# * Evaluate like `pp <expr>` on the current frame.
register_command 'pp' do |arg|
request_eval :pp, arg.to_s
end
# * `eval <expr>`
# * Evaluate `<expr>` on the current frame.
register_command 'eval', 'call' do |arg|
if arg == nil || arg.empty?
show_help 'eval'
@ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
:retry
else
request_eval :call, arg
end
end
# * `irb`
# * Invoke `irb` on the current frame.
register_command 'irb' do |arg|
if @ui.remote?
@ui.puts "\nIRB is not supported on the remote console."
:retry
else
request_eval :irb, nil
end
end
### Trace
# * `trace`
# * Show available tracers list.
# * `trace line`
# * Add a line tracer. It indicates line events.
# * `trace call`
# * Add a call tracer. It indicate call/return events.
# * `trace exception`
# * Add an exception tracer. It indicates raising exceptions.
# * `trace object <expr>`
# * Add an object tracer. It indicates that an object by `<expr>` is passed as a parameter or a receiver on method call.
# * `trace ... /regexp/`
# * Indicates only matched events to `/regexp/`.
# * `trace ... into: <file>`
# * Save trace information into: `<file>`.
# * `trace off <num>`
# * Disable tracer specified by `<num>` (use `trace` command to check the numbers).
# * `trace off [line|call|pass]`
# * Disable all tracers. If `<type>` is provided, disable specified type tracers.
register_command 'trace', postmortem: false, unsafe: false do |arg|
if (re = /\s+into:\s*(.+)/) =~ arg
into = $1
arg.sub!(re, '')
end
if (re = /\s\/(.+)\/\z/) =~ arg
pattern = $1
arg.sub!(re, '')
end
case arg
when nil
@ui.puts 'Tracers:'
@tracers.values.each_with_index{|t, i|
@ui.puts "* \##{i} #{t}"
}
@ui.puts
:retry
when /\Aline\z/
add_tracer LineTracer.new(@ui, pattern: pattern, into: into)
:retry
when /\Acall\z/
add_tracer CallTracer.new(@ui, pattern: pattern, into: into)
:retry
when /\Aexception\z/
add_tracer ExceptionTracer.new(@ui, pattern: pattern, into: into)
:retry
when /\Aobject\s+(.+)/
request_tc_with_restarted_threads [:trace, :object, $1.strip, {pattern: pattern, into: into}]
when /\Aoff\s+(\d+)\z/
if t = @tracers.values[$1.to_i]
t.disable
@ui.puts "Disable #{t.to_s}"
else
@ui.puts "Unmatched: #{$1}"
end
:retry
when /\Aoff(\s+(line|call|exception|object))?\z/
@tracers.values.each{|t|
if $2.nil? || t.type == $2
t.disable
@ui.puts "Disable #{t.to_s}"
end
}
:retry
else
@ui.puts "Unknown trace option: #{arg.inspect}"
:retry
end
end
# Record
# * `record`
# * Show recording status.
# * `record [on|off]`
# * Start/Stop recording.
# * `step back`
# * Start replay. Step back with the last execution log.
# * `s[tep]` does stepping forward with the last log.
# * `step reset`
# * Stop replay .
register_command 'record', postmortem: false, unsafe: false do |arg|
case arg
when nil, 'on', 'off'
request_tc [:record, arg&.to_sym]
else
@ui.puts "unknown command: #{arg}"
:retry
end
end
### Thread control
# * `th[read]`
# * Show all threads.
# * `th[read] <thnum>`
# * Switch thread specified by `<thnum>`.
register_command 'th', 'thread', unsafe: false do |arg|
case arg
when nil, 'list', 'l'
thread_list
when /(\d+)/
switch_thread $1.to_i
else
@ui.puts "unknown thread command: #{arg}"
end
:retry
end
### Configuration
# * `config`
# * Show all configuration with description.
# * `config <name>`
# * Show current configuration of <name>.
# * `config set <name> <val>` or `config <name> = <val>`
# * Set <name> to <val>.
# * `config append <name> <val>` or `config <name> << <val>`
# * Append `<val>` to `<name>` if it is an array.
# * `config unset <name>`
# * Set <name> to default.
register_command 'config', unsafe: false do |arg|
config_command arg
:retry
end
# * `source <file>`
# * Evaluate lines in `<file>` as debug commands.
register_command 'source' do |arg|
if arg
begin
cmds = File.readlines(path = File.expand_path(arg))
add_preset_commands path, cmds, kick: true, continue: false
rescue Errno::ENOENT
@ui.puts "File not found: #{arg}"
end
else
show_help 'source'
end
:retry
end
# * `open`
# * open debuggee port on UNIX domain socket and wait for attaching.
# * Note that `open` command is EXPERIMENTAL.
# * `open [<host>:]<port>`
# * open debuggee port on TCP/IP with given `[<host>:]<port>` and wait for attaching.
# * `open vscode`
# * open debuggee port for VSCode and launch VSCode if available.
# * `open chrome`
# * open debuggee port for Chrome and wait for attaching.
register_command 'open' do |arg|
case arg&.downcase
when '', nil
::DEBUGGER__.open nonstop: true
when /\A(\d+)z/
::DEBUGGER__.open_tcp host: nil, port: $1.to_i, nonstop: true
when /\A(.+):(\d+)\z/
::DEBUGGER__.open_tcp host: $1, port: $2.to_i, nonstop: true
when 'tcp'
::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
when 'vscode'
CONFIG[:open] = 'vscode'
::DEBUGGER__.open nonstop: true
when 'chrome', 'cdp'
CONFIG[:open] = 'chrome'
::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
else
raise "Unknown arg: #{arg}"
end
:retry
end
### Help
# * `h[elp]`
# * Show help for all commands.
# * `h[elp] <command>`
# * Show help for the given command.
register_command 'h', 'help', '?', unsafe: false do |arg|
show_help arg
:retry
end
end