An Attempt at Adding Custom Commands and Aliases to ruby debugger - blitterated/groove_automator GitHub Wiki
ruby/debug command processing as of v1.9.1
How Session is created and set:
class << self
define_method :initialize_session do |&init_ui|
DEBUGGER__.info "Session start (pid: #{Process.pid})"
::DEBUGGER__.const_set(:SESSION, Session.new)
SESSION.activate init_ui.call
load_rc
end
endDefinition of SessionCommand:
SessionCommand = Struct.new(:block, :repeat, :unsafe, :cancel_auto_continue, :postmortem)Definition of @commands:
@commands = {}Definition of register_command:
private def register_command *names,
repeat: false, unsafe: true, cancel_auto_continue: false, postmortem: true,
&b
cmd = SessionCommand.new(b, repeat, unsafe, cancel_auto_continue, postmortem)
names.each{|name|
@commands[name] = cmd
}
endDefinition of register_default_command:
# too big to listDefinition of process_command:
def process_command line
if line.empty?
if @repl_prev_line
line = @repl_prev_line
else
return :retry
end
else
@repl_prev_line = line
end
/([^\s]+)(?:\s+(.+))?/ =~ line
cmd_name, cmd_arg = $1, $2
if cmd = @commands[cmd_name]
check_postmortem if !cmd.postmortem
check_unsafe if cmd.unsafe
cancel_auto_continue if cmd.cancel_auto_continue
@repl_prev_line = nil if !cmd.repeat
cmd.block.call(cmd_arg)
else
@repl_prev_line = nil
check_unsafe
request_eval :pp, line
end
rescue Interrupt
return :retry
rescue SystemExit
raise
rescue PostmortemError => e
@ui.puts e.message
return :retry
rescue Exception => e
@ui.puts "[REPL ERROR] #{e.inspect}"
@ui.puts e.backtrace.map{|e| ' ' + e}
return :retry
end- Can we get access to the current
Sessioninstance? - Can we monkey patch the
Sessionclass or current instance? - Can we add register a command with the current
Session?
The Session instance gets created and stuffed into the SESSION const in the DEBUGGER__ module.
Definition of DEBUGGER__.initialize_session:
class << self
define_method :initialize_session do |&init_ui|
DEBUGGER__.info "Session start (pid: #{Process.pid})"
::DEBUGGER__.const_set(:SESSION, Session.new)
SESSION.activate init_ui.call
load_rc
end
endTest:
[1] pry(main)> DEBUGGER__::SESSION.inspect
=> "DEBUGGER__::SESSION"
👍🏻
Since DEBUGGER__::Session#register_command is private, we can monkey patch a wrapper method into the class.
Add to .pryrc:
if defined?(DEBUGGER__)
puts "ruby/debugger loaded."
class DEBUGGER__::Session
def foobar
puts " ,,,\n (o o)\n---ooO-(_)-Ooo---"
"baz wuz hur."
end
end
endRun Pry and test:
ruby/debugger loaded.
[1] pry(main)> DEBUGGER__::SESSION.foobar
,,,
(o o)
---ooO-(_)-Ooo---
=> "baz wuz hur."
👍🏻
Remove the definition of foobar from .pryrc, and add the monkey patch and a method to clone a SessionCommand.
if defined?(DEBUGGER__)
puts "ruby/debugger loaded."
class DEBUGGER__::Session
def register_my_command *names, repeat:, unsafe:,
cancel_auto_continue:, postmortem:,
&b
register_command *names, repeat, unsafe,
cancel_auto_continue, postmortem,
&b
end
def clone_command(cmd_name)
cmd = @commands[cmd_name]
if cmd.nil?
puts "Command \"#{cmd_name}\" is not a registered command."
nil
else
#SessionCommand.new(cmd.block, cmd.repeat, cmd.unsafe,
# cmd.cancel_auto_continue, cmd.postmortem)
cmd.clone
end
end
end
endAdd to .pryrc following DEBUGGER__::Session monkey patch:
DEBUGGER__::SESSION.register_my_command 'hw', "helloworld",
repeat: true do |arg|
puts "Hello world!"
endUnfortunately, it barfs all over the place.
(rdbg) hw
Hello world!
nil
#<fatal:"No live threads left. Deadlock?\n2 threads, 2 sleeps current:0x0000aaaab0d53bf0 main thread:0x0000aaaab029f2a0\n* #<Thread:0x0000ffff6ec8aa28 sleep_forever>\n rb_thread_t:0x0000aaaab029f2a0 native:0x0000ffff8855d4c0 int:0\n \n* #<Thread:0x0000ffff6cde2d50@DEBUGGER__::SESSION@server /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/session.rb:179 sleep_forever>\n rb_thread_t:0x0000aaaab0d53bf0 native:0x0000ffff6caaf100 int:0\n \n">
@@@ #<Thread:0x0000ffff6ec8aa28 run>
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/thread_client.rb:1251:in `backtrace'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/thread_client.rb:1251:in `block in wait_next_action_'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/thread_client.rb:1249:in `each'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/thread_client.rb:1249:in `rescue in wait_next_action_'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/thread_client.rb:1244:in `wait_next_action_'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/thread_client.rb:875:in `block in wait_next_action'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/thread_client.rb:866:in `block in fiber_blocking'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/thread_client.rb:866:in `blocking'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/thread_client.rb:866:in `fiber_blocking'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/thread_client.rb:875:in `wait_next_action'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/thread_client.rb:320:in `suspend'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/thread_client.rb:358:in `block in step_tp'
> /src/src/groove_automator.rb:174:in `block in remove_kick_on_snare'
> /src/src/groove_automator.rb:165:in `each'
> /src/src/groove_automator.rb:165:in `each_with_index'
> /src/src/groove_automator.rb:165:in `remove_kick_on_snare'
> /src/src/groove_automator.rb:291:in `groove_345c'
> (pry):1:in `__pry__'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/pry_instance.rb:290:in `eval'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/pry_instance.rb:290:in `evaluate_ruby'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/pry_instance.rb:659:in `handle_line'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/pry_instance.rb:261:in `block (2 levels) in eval'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/pry_instance.rb:260:in `catch'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/pry_instance.rb:260:in `block in eval'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/pry_instance.rb:259:in `catch'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/pry_instance.rb:259:in `eval'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/repl.rb:77:in `block in repl'
> <internal:kernel>:187:in `loop'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/repl.rb:67:in `repl'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/repl.rb:38:in `block in start'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/input_lock.rb:61:in `__with_ownership'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/input_lock.rb:78:in `with_ownership'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/repl.rb:38:in `start'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/repl.rb:15:in `start'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/pry_class.rb:194:in `start'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/lib/pry/cli.rb:112:in `start'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/pry-0.14.2/bin/pry:13:in `<top (required)>'
> /opt/rubies/ruby-3.3.0/bin/pry:25:in `load'
> /opt/rubies/ruby-3.3.0/bin/pry:25:in `<main>'
@@@ #<Thread:0x0000ffff6cde2d50@DEBUGGER__::SESSION@server /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/session.rb:179 sleep_forever>
> <internal:thread_sync>:18:in `pop'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/session.rb:249:in `pop_event'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/session.rb:253:in `session_server_main'
> /opt/rubies/ruby-3.3.0/lib/ruby/gems/3.3.0/gems/debug-1.9.1/lib/debug/session.rb:212:in `block in activate'```
### `SessionCommand` properties, values, and defaults
#### `SessionCommand` properties
| Property | Definition |
| -------- | ---------- |
| `:repeat` | Command will repeat each time the user enters a new line |
| `:unsafe` | Command allowed/denied in Unsafe Context |
| `:cancel_auto_continue` | ??? |
| `:postmortem` | Command allowed/denied for [Post-mortem Debugging](https://web.archive.org/web/20070103104458/http://www.datanoise.com/articles/2006/12/20/post-mortem-debugging). Debug process after fatal exception. |
These get checked in [`Session#process_command`](https://github.com/ruby/debug/blob/9de0ff46faeea73aed27bb090ee052db8fb74c14/lib/debug/session.rb#L1157-L1163):
```ruby
if cmd = @commands[cmd_name]
check_postmortem if !cmd.postmortem
check_unsafe if cmd.unsafe
cancel_auto_continue if cmd.cancel_auto_continue
@repl_prev_line = nil if !cmd.repeat
cmd.block.call(cmd_arg)
| Context | :repeat |
:unsafe |
:cancel_auto_continue |
:postmortem |
|---|---|---|---|---|
register_command param defaults |
false |
true |
false |
true |
register_default_command:next
|
true |
true* |
true |
false |
register_default_command:info
|
false* |
false |
false* |
true* |
* means default is used
definition: n[ext]
definition: i[nfo]
definition: i[nfo] l[ocals]
register_my_command 'nl',
repeat: true,
cancel_auto_continue: true,
postmortem: false do |arg|
# TODO: `next` can take an int arg, and `info` can take a regex arg
# Run to next line
next_line = "next"
next_line += " #{next_arg}" unless next_arg.nil? or next_arg.empty?
process_command next_line
# Show local variables
info_locals_line = "info locals"
info_locals_line += " #{info_locals_arg}" unless info_locals_arg.nil? or info_locals_arg.empty?
process_command info_locals_line
endnot quite there yet...