debug 1.0.0.beta3 → 1.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9702de02ad9d9aab11e0f0113a3596110222f92178e2918e6f4c5ad66fb5e3fb
4
- data.tar.gz: 6f8552da80d13f0c597223fa651a49f44810d7106feb73c8a93d73249c74a987
3
+ metadata.gz: ebaae12c10e51036f850fd1cbcde66d891a1e50b64bc5d96bfdab610cb42fd1d
4
+ data.tar.gz: e62eee365aa1409c650049ccb8ed237854aeed6f7a066dc2b84f96c9cffefd12
5
5
  SHA512:
6
- metadata.gz: 1a2386cd5fcde06d356cb35e1d5ff637c587c33a4fdf9e3866d11761276a31cf5586d383b8cd8f6074be24a681592db7d577b8fcf3270d63afff9256ddaaa78f
7
- data.tar.gz: 373039bc43b6071d77c4bb103453258a50668224ea581b4fd0b5ec0bf3df7c4b95702e2bc3723e35c55e238aaf6fa9297090fa7daf8fe4c6eee22124f1087daf
6
+ metadata.gz: 2aa2b044268220dc79d72850f6366474fff647fa6893360044ce64a8a97e4d759e414da6c39dac52a6b33593512675091ed4fb4a7399c1357b344d342f3c812f
7
+ data.tar.gz: 506d0afeb3e3926da6631e99234fd2ba4c96220d5def5a95d0e51afa54ae28beb19c30873c3c0cf9a95fa975023364f7088a0190d26452e307e715925e21a78c
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  /tmp/
9
9
  *.bundle
10
10
  /Gemfile.lock
11
+ /lib/debug/debug.so
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,97 @@
1
+ ## Manually Test Your Changes
2
+
3
+ You can manually test your changes with a simple Ruby script + a line of command. The following example will help you check:
4
+
5
+ - Breakpoint insertion.
6
+ - Resume from the breakpoint.
7
+ - Backtrace display.
8
+ - Information (local variables, ivars..etc.) display.
9
+ - Debugger exit.
10
+
11
+
12
+ ### Script
13
+
14
+ ```ruby
15
+ # target.rb
16
+ class Foo
17
+ def first_call
18
+ second_call(20)
19
+ end
20
+
21
+ def second_call(num)
22
+ third_call_with_block do |ten|
23
+ forth_call(num, ten)
24
+ end
25
+ end
26
+
27
+ def third_call_with_block(&block)
28
+ @ivar1 = 10; @ivar2 = 20
29
+
30
+ yield(10)
31
+ end
32
+
33
+ def forth_call(num1, num2)
34
+ num1 + num2
35
+ end
36
+ end
37
+
38
+ Foo.new.first_call
39
+ ```
40
+
41
+ ### Command
42
+
43
+ ```
44
+ $ exe/rdbg -e 'b 20;; c ;; bt ;; info ;; q!' -e c target.rb
45
+ ```
46
+
47
+ ### Expect Result
48
+
49
+ ```
50
+ ❯ exe/rdbg -e 'b 20;; c ;; bt ;; info ;; q!' -e c target.rb
51
+ [1, 10] in target.rb
52
+ => 1| class Foo
53
+ 2| def first_call
54
+ 3| second_call(20)
55
+ 4| end
56
+ 5|
57
+ 6| def second_call(num)
58
+ 7| third_call_with_block do |ten|
59
+ 8| forth_call(num, ten)
60
+ 9| end
61
+ 10| end
62
+ =>#0 <main> at target.rb:1
63
+ (rdbg:init) b 20
64
+ #1 line bp /PATH_TO_PROJECT/debug/target.rb:20 (return)
65
+ (rdbg:init) c
66
+ [15, 23] in target.rb
67
+ 15| yield(10)
68
+ 16| end
69
+ 17|
70
+ 18| def forth_call(num1, num2)
71
+ 19| num1 + num2
72
+ => 20| end
73
+ 21| end
74
+ 22|
75
+ 23| Foo.new.first_call
76
+ =>#0 Foo#forth_call(num1=20, num2=10) at target.rb:20 #=> 30
77
+ #1 block{|ten=10|} in second_call at target.rb:8
78
+ # and 4 frames (use `bt' command for all frames)
79
+
80
+ Stop by #1 line bp /PATH_TO_PROJECT/debug/target.rb:20 (return)
81
+ (rdbg:init) bt
82
+ =>#0 Foo#forth_call(num1=20, num2=10) at target.rb:20 #=> 30
83
+ #1 block{|ten=10|} in second_call at target.rb:8
84
+ #2 Foo#third_call_with_block(block=#<Proc:0x00007f8bc32f0c28 target.rb:7>) at target.rb:15
85
+ #3 Foo#second_call(num=20) at target.rb:7
86
+ #4 first_call at target.rb:3
87
+ #5 <main> at target.rb:23
88
+ (rdbg:init) info
89
+ =>#0 Foo#forth_call(num1=20, num2=10) at target.rb:20 #=> 30
90
+ %self => #<Foo:0x00007f8bc32f0ed0>
91
+ %return => 30
92
+ num1 => 20
93
+ num2 => 10
94
+ @ivar1 => 10
95
+ @ivar2 => 20
96
+ (rdbg:init) q!
97
+ ```
data/Gemfile CHANGED
@@ -2,6 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- gem "rake", "~> 12.0"
5
+ gem "rake"
6
6
  gem "rake-compiler"
7
7
  gem "minitest", "~> 5.0"
data/README.md CHANGED
@@ -62,13 +62,14 @@ $ ruby -r debug/run target.rb
62
62
 
63
63
  $ cat target.rb
64
64
  require 'debug/run' # start the debug console
65
- ...
65
+ ... rest of program ...
66
66
 
67
67
  # or
68
68
 
69
69
  $ cat target.rb
70
70
  require 'debug/session' # introduce the functionality
71
71
  DEBUGGER__.console # and start the debug console
72
+ ... rest of program ...
72
73
 
73
74
  $ ruby target.rb
74
75
  ```
@@ -373,11 +374,11 @@ The `<...>` notation means the argument.
373
374
  * Note that edited file will not be reloaded.
374
375
  * `edit <file>`
375
376
  * Open <file> on the editor.
376
- * `i[nfo]`
377
+ * `i[nfo]`, `i[nfo] l[ocal[s]]`
377
378
  * Show information about the current frame (local variables)
378
379
  * It includes `self` as `%self` and a return value as `%return`.
379
- * `i[nfo] <expr>`
380
- * Show information about the result of <expr>.
380
+ * `i[nfo] th[read[s]]
381
+ * Show all threads (same as `th[read]`).
381
382
  * `display`
382
383
  * Show display setting.
383
384
  * `display <expr>`
@@ -437,27 +438,28 @@ Debug console mode:
437
438
  -O, --open Start debuggee with opening the debugger port.
438
439
  If TCP/IP options are not given,
439
440
  a UNIX domain socket will be used.
441
+ --sock-path=[SOCK_PATH] UNIX Doman socket path
440
442
  --port=[PORT] Listening TCP/IP port
441
443
  --host=[HOST] Listening TCP/IP host
442
444
 
443
445
  Debug console mode runs Ruby program with the debug console.
444
446
 
445
- rdbg target.rb foo bar starts like 'ruby target.rb foo bar'.
446
- rdbg -- -r foo -e bar starts like 'ruby -r foo -e bar'.
447
- rdbg -O target.rb foo bar starts and accepts attaching with UNIX domain socket.
448
- rdbg -O --port 1234 target.rb foo bar starts accepts attaching with TCP/IP localhost:1234.
449
- rdbg -O --port 1234 -- -r foo -e bar starts accepts attaching with TCP/IP localhost:1234.
447
+ 'rdbg target.rb foo bar' starts like 'ruby target.rb foo bar'.
448
+ 'rdbg -- -r foo -e bar' starts like 'ruby -r foo -e bar'.
449
+ 'rdbg -O target.rb foo bar' starts and accepts attaching with UNIX domain socket.
450
+ 'rdbg -O --port 1234 target.rb foo bar' starts accepts attaching with TCP/IP localhost:1234.
451
+ 'rdbg -O --port 1234 -- -r foo -e bar' starts accepts attaching with TCP/IP localhost:1234.
450
452
 
451
453
  Attach mode:
452
454
  -A, --attach Attach to debuggee process.
453
455
 
454
456
  Attach mode attaches the remote debug console to the debuggee process.
455
457
 
456
- 'rdbg -A' tries to connect via UNIX domain socket.
457
- If there are multiple processes are waiting for the
458
- debugger connection, list possible debuggee names.
459
- 'rdbg -A path' tries to connect via UNIX domain socket with given path name.
460
- 'rdbg -A port' tries to connect to localhost:port via TCP/IP.
458
+ 'rdbg -A' tries to connect via UNIX domain socket.
459
+ If there are multiple processes are waiting for the
460
+ debugger connection, list possible debuggee names.
461
+ 'rdbg -A path' tries to connect via UNIX domain socket with given path name.
462
+ 'rdbg -A port' tries to connect to localhost:port via TCP/IP.
461
463
  'rdbg -A host port' tries to connect to host:port via TCP/IP.
462
464
 
463
465
  ```
@@ -465,3 +467,5 @@ Attach mode:
465
467
  # Contributing
466
468
 
467
469
  Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/debug.
470
+
471
+ Please also check the [contributing guideline](/CONTRIBUTING.md).
data/TODO.md ADDED
@@ -0,0 +1,33 @@
1
+ # TODO
2
+
3
+ ## Basic functionality
4
+
5
+ * VScode DAP support
6
+ * Support Ractors
7
+ * Signal (SIGINT) support
8
+ * Remote connection with openssl
9
+
10
+ ## UI
11
+
12
+ * Coloring
13
+ * Interactive breakpoint setting
14
+ * irb integration
15
+ * Web browser integrated UI
16
+
17
+ ## Debug command
18
+
19
+ * Breakpoints
20
+ * Lightweight pending method break points with Ruby 3.1 feature (TP:method_added)
21
+ * Non-stop breakpoint but runs some code.
22
+ * Watch points
23
+ * Lightweight watchpoints for instance variables with Ruby 3.1 features (TP:ivar_set)
24
+ * Faster `next`/`finish` command by specifying target code.
25
+ * `set`/`show` configurations
26
+ * In-memory line traces
27
+ * Timemachine debugging
28
+
29
+ ## Tests
30
+
31
+ * Test framework
32
+ * Tests for commands
33
+ * Tests for remote debugging
data/ext/debug/debug.c CHANGED
@@ -90,7 +90,7 @@ method_added_tracker(VALUE tpval, void *ptr)
90
90
  VALUE mid = rb_tracearg_callee_id(arg);
91
91
 
92
92
  if (RB_UNLIKELY(mid == ID2SYM(rb_intern("method_added")) ||
93
- mid == ID2SYM(rb_intern("singleton_method_method_added")))) {
93
+ mid == ID2SYM(rb_intern("singleton_method_added")))) {
94
94
  VALUE args[] = {
95
95
  tpval,
96
96
  };
data/lib/debug.rb CHANGED
@@ -1 +1 @@
1
- require_relative 'debug/run'
1
+ require_relative 'debug/session'
@@ -209,7 +209,7 @@ module DEBUGGER__
209
209
  @tp = TracePoint.new(:raise){|tp|
210
210
  exc = tp.raised_exception
211
211
  exc.class.ancestors.each{|cls|
212
- suspend if pat === cls.name
212
+ suspend if @pat === cls.name
213
213
  }
214
214
  }
215
215
  end
@@ -333,7 +333,11 @@ module DEBUGGER__
333
333
  end
334
334
  end
335
335
 
336
- def enable quiet: false
336
+ def enable
337
+ try_enable
338
+ end
339
+
340
+ def try_enable quiet: false
337
341
  eval_class_name
338
342
  search_method
339
343
 
data/lib/debug/config.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  module DEBUGGER__
3
3
  def self.unix_domain_socket_dir
4
4
  case
5
- when path = DEBUGGER__::CONFIG[:sock_dir]
5
+ when path = ::DEBUGGER__::CONFIG[:sock_dir]
6
6
  when path = ENV['XDG_RUNTIME_DIR']
7
7
  when home = ENV['HOME']
8
8
  path = File.join(home, '.ruby-debug-sock')
@@ -40,25 +40,39 @@ module DEBUGGER__
40
40
  show_frames: 'RUBY_DEBUG_SHOW_FRAMES', # Show n frames on breakpoint (default: 2 frames).
41
41
  use_short_path: 'RUBY_DEBUG_USE_SHORT_PATH', # Show shoten PATH (like $(Gem)/foo.rb).
42
42
  skip_nosrc: 'RUBY_DEBUG_SKIP_NOSRC', # Skip on no source code lines (default: false).
43
+ use_colorize: 'RUBY_DEBUG_USE_COLORIZE', # Show coloring
43
44
 
44
45
  # remote
45
46
  port: 'RUBY_DEBUG_PORT', # TCP/IP remote debugging: port
46
47
  host: 'RUBY_DEBUG_HOST', # TCP/IP remote debugging: host (localhost if not given)
48
+ sock_path: 'RUBY_DEBUG_SOCK_PATH', # UNIX Domain Socket remote debugging: socket path
47
49
  sock_dir: 'RUBY_DEBUG_SOCK_DIR', # UNIX Domain Socket remote debugging: socket directory
48
50
  }.freeze
49
51
 
50
52
  def self.config_to_env config
51
53
  CONFIG_MAP.each{|key, evname|
52
- ENV[evname] = config[key]
54
+ ENV[evname] = config[key].to_s if config[key]
53
55
  }
54
56
  end
55
57
 
56
58
  def self.parse_argv argv
57
59
  config = {
58
60
  mode: :start,
61
+ use_colorize: true,
59
62
  }
60
63
  CONFIG_MAP.each{|key, evname|
61
- config[key] = ENV[evname] if ENV[evname]
64
+ if val = ENV[evname]
65
+ if /_USE_/ =~ evname || /NONSTOP/ =~ evname
66
+ case val
67
+ when '1', 'true', 'TRUE', 'T'
68
+ config[key] = true
69
+ else
70
+ config[key] = false
71
+ end
72
+ else
73
+ config[key] = val
74
+ end
75
+ end
62
76
  }
63
77
  return config if !argv || argv.empty?
64
78
 
@@ -83,6 +97,9 @@ module DEBUGGER__
83
97
  'a UNIX domain socket will be used.') do
84
98
  config[:remote] = true
85
99
  end
100
+ o.on('--sock-path=[SOCK_PATH]', 'UNIX Doman socket path') do |path|
101
+ config[:sock_path] = path
102
+ end
86
103
  o.on('--port=[PORT]', 'Listening TCP/IP port') do |port|
87
104
  config[:port] = port
88
105
  end
@@ -95,11 +112,11 @@ module DEBUGGER__
95
112
  o.separator ''
96
113
  o.separator ' Debug console mode runs Ruby program with the debug console.'
97
114
  o.separator ''
98
- o.separator " #{rdbg} target.rb foo bar starts like 'ruby target.rb foo bar'."
99
- o.separator " #{rdbg} -- -r foo -e bar starts like 'ruby -r foo -e bar'."
100
- o.separator " #{rdbg} -O target.rb foo bar starts and accepts attaching with UNIX domain socket."
101
- o.separator " #{rdbg} -O --port 1234 target.rb foo bar starts accepts attaching with TCP/IP localhost:1234."
102
- o.separator " #{rdbg} -O --port 1234 -- -r foo -e bar starts accepts attaching with TCP/IP localhost:1234."
115
+ o.separator " '#{rdbg} target.rb foo bar' starts like 'ruby target.rb foo bar'."
116
+ o.separator " '#{rdbg} -- -r foo -e bar' starts like 'ruby -r foo -e bar'."
117
+ o.separator " '#{rdbg} -O target.rb foo bar' starts and accepts attaching with UNIX domain socket."
118
+ o.separator " '#{rdbg} -O --port 1234 target.rb foo bar' starts accepts attaching with TCP/IP localhost:1234."
119
+ o.separator " '#{rdbg} -O --port 1234 -- -r foo -e bar' starts accepts attaching with TCP/IP localhost:1234."
103
120
 
104
121
  o.separator ''
105
122
  o.separator 'Attach mode:'
@@ -110,11 +127,11 @@ module DEBUGGER__
110
127
  o.separator ''
111
128
  o.separator ' Attach mode attaches the remote debug console to the debuggee process.'
112
129
  o.separator ''
113
- o.separator " '#{rdbg} -A' tries to connect via UNIX domain socket."
114
- o.separator " #{' ' * rdbg.size} If there are multiple processes are waiting for the"
115
- o.separator " #{' ' * rdbg.size} debugger connection, list possible debuggee names."
116
- o.separator " '#{rdbg} -A path' tries to connect via UNIX domain socket with given path name."
117
- o.separator " '#{rdbg} -A port' tries to connect to localhost:port via TCP/IP."
130
+ o.separator " '#{rdbg} -A' tries to connect via UNIX domain socket."
131
+ o.separator " #{' ' * rdbg.size} If there are multiple processes are waiting for the"
132
+ o.separator " #{' ' * rdbg.size} debugger connection, list possible debuggee names."
133
+ o.separator " '#{rdbg} -A path' tries to connect via UNIX domain socket with given path name."
134
+ o.separator " '#{rdbg} -A port' tries to connect to localhost:port via TCP/IP."
118
135
  o.separator " '#{rdbg} -A host port' tries to connect to host:port via TCP/IP."
119
136
  end
120
137
 
@@ -122,4 +139,16 @@ module DEBUGGER__
122
139
 
123
140
  config
124
141
  end
142
+
143
+ CONFIG = ::DEBUGGER__.parse_argv(ENV['RUBY_DEBUG_OPT'])
144
+
145
+ def self.set_config kw
146
+ kw.each{|k, v|
147
+ if CONFIG_MAP[k]
148
+ CONFIG[k] = v # TODO: ractor support
149
+ else
150
+ raise "unknown option: #{k}"
151
+ end
152
+ }
153
+ end
125
154
  end
@@ -0,0 +1,113 @@
1
+
2
+ module DEBUGGER__
3
+ FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
4
+ :has_return_value, :return_value, :show_line)
5
+
6
+
7
+ # extend FrameInfo with debug.so
8
+ if File.exist? File.join(__dir__, 'debug.so')
9
+ require_relative 'debug.so'
10
+ else
11
+ require "debug/debug"
12
+ end
13
+
14
+ class FrameInfo
15
+ HOME = ENV['HOME'] ? (ENV['HOME'] + '/') : nil
16
+
17
+ def path
18
+ location.path
19
+ end
20
+
21
+ def realpath
22
+ location.absolute_path
23
+ end
24
+
25
+ def pretty_path
26
+ use_short_path = ::DEBUGGER__::CONFIG[:use_short_path]
27
+
28
+ case
29
+ when use_short_path && path.start_with?(dir = ::DEBUGGER__::CONFIG["rubylibdir"] + '/')
30
+ path.sub(dir, '$(rubylibdir)/')
31
+ when use_short_path && Gem.path.any? do |gp|
32
+ path.start_with?(dir = gp + '/gems/')
33
+ end
34
+ path.sub(dir, '$(Gem)/')
35
+ when HOME && path.start_with?(HOME)
36
+ path.sub(HOME, '~/')
37
+ else
38
+ path
39
+ end
40
+ end
41
+
42
+ def file_lines
43
+ SESSION.source(realpath || path)
44
+ end
45
+
46
+ def call_identifier_str
47
+ if binding && iseq
48
+ if iseq.type == :block
49
+ if (argc = iseq.argc) > 0
50
+ args = parameters_info iseq.locals[0...argc]
51
+ args_str = "{|#{args}|}"
52
+ end
53
+
54
+ location.label.sub('block'){ "block#{args_str}" }
55
+ elsif (callee = binding.eval('__callee__', __FILE__, __LINE__)) && (argc = iseq.argc) > 0
56
+ args = parameters_info iseq.locals[0...argc]
57
+ "#{klass_sig}#{callee}(#{args})"
58
+ else
59
+ location.label
60
+ end
61
+ else
62
+ "[C] #{klass_sig}#{location.base_label}"
63
+ end
64
+ end
65
+
66
+ def return_str
67
+ if binding && iseq && has_return_value
68
+ short_inspect(return_value)
69
+ end
70
+ end
71
+
72
+ def location_str
73
+ "#{pretty_path}:#{location.lineno}"
74
+ end
75
+
76
+ private
77
+
78
+ SHORT_INSPECT_LENGTH = 40
79
+
80
+ def short_inspect obj
81
+ str = obj.inspect
82
+ if str.length > SHORT_INSPECT_LENGTH
83
+ str[0...SHORT_INSPECT_LENGTH] + '...'
84
+ else
85
+ str
86
+ end
87
+ end
88
+
89
+ def get_singleton_class obj
90
+ obj.singleton_class # TODO: don't use it
91
+ rescue TypeError
92
+ nil
93
+ end
94
+
95
+ def parameters_info vars
96
+ vars.map{|var|
97
+ begin
98
+ "#{var}=#{short_inspect(binding.local_variable_get(var))}"
99
+ rescue NameError, TypeError
100
+ nil
101
+ end
102
+ }.compact.join(', ')
103
+ end
104
+
105
+ def klass_sig
106
+ if self.class == get_singleton_class(self.self)
107
+ "#{self.self}."
108
+ else
109
+ "#{self.class}#"
110
+ end
111
+ end
112
+ end
113
+ end
data/lib/debug/server.rb CHANGED
@@ -187,39 +187,30 @@ module DEBUGGER__
187
187
  end
188
188
 
189
189
  class UI_UnixDomainServer < UI_ServerBase
190
- def initialize sock_dir: nil
190
+ def initialize sock_dir: nil, sock_path: nil
191
+ @sock_path = sock_path
191
192
  @sock_dir = sock_dir || DEBUGGER__.unix_domain_socket_dir
192
193
 
193
194
  super()
194
195
  end
195
196
 
196
197
  def accept
197
- @file = DEBUGGER__.create_unix_domain_socket_name(@sock_dir)
198
+ case
199
+ when @sock_path
200
+ when sp = ::DEBUGGER__::CONFIG[:sock_path]
201
+ @sock_path = sp
202
+ else
203
+ @sock_path = DEBUGGER__.create_unix_domain_socket_name(@sock_dir)
204
+ end
198
205
 
199
- ::DEBUGGER__.message "Debugger can attach via UNIX domain socket (#{@file})"
200
- Socket.unix_server_loop @file do |sock, client|
206
+ ::DEBUGGER__.message "Debugger can attach via UNIX domain socket (#{@sock_path})"
207
+ Socket.unix_server_loop @sock_path do |sock, client|
201
208
  @client_addr = client
202
209
  yield sock
203
210
  end
204
211
  end
205
212
  end
206
213
 
207
- def self.open host: nil, port: ::DEBUGGER__::CONFIG[:port], sock_dir: nil
208
- if port
209
- open_tcp host: host, port: port
210
- else
211
- open_unix sock_dir: sock_dir
212
- end
213
- end
214
-
215
- def self.open_tcp(host: nil, port:)
216
- initialize_session UI_TcpServer.new(host: host, port: port)
217
- end
218
-
219
- def self.open_unix sock_dir: nil
220
- initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir)
221
- end
222
-
223
214
  def self.message msg
224
215
  $stderr.puts "DEBUGGER: #{msg}"
225
216
  end
data/lib/debug/session.rb CHANGED
@@ -1,18 +1,7 @@
1
- module DEBUGGER__
2
- # used in thread_client.c
3
- FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
4
- :has_return_value, :return_value, :show_line)
5
- end
6
-
7
- if File.exist? File.join(__dir__, 'debug.so')
8
- require_relative 'debug.so'
9
- else
10
- require "debug/debug"
11
- end
12
-
1
+ 
2
+ require_relative 'thread_client'
13
3
  require_relative 'source_repository'
14
4
  require_relative 'breakpoint'
15
- require_relative 'thread_client'
16
5
  require_relative 'config'
17
6
 
18
7
  class RubyVM::InstructionSequence
@@ -61,7 +50,7 @@ module DEBUGGER__
61
50
  @bps = {} # bp.key => bp
62
51
  # [file, line] => LineBreakpoint
63
52
  # "Error" => CatchBreakpoint
64
- # Method => MethodBreakpoint
53
+ # "Foo#bar" => MethodBreakpoint
65
54
  # [:watch, expr] => WatchExprBreakpoint
66
55
  # [:check, expr] => CheckBreakpoint
67
56
  @th_clients = {} # {Thread => ThreadClient}
@@ -73,7 +62,8 @@ module DEBUGGER__
73
62
 
74
63
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
75
64
  ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
76
- }.enable
65
+ }
66
+ @tp_load_script.enable
77
67
 
78
68
  @session_server = Thread.new do
79
69
  Thread.current.abort_on_exception = true
@@ -149,7 +139,8 @@ module DEBUGGER__
149
139
 
150
140
  @tp_thread_begin = TracePoint.new(:thread_begin){|tp|
151
141
  ThreadClient.current.on_thread_begin Thread.current
152
- }.enable
142
+ }
143
+ @tp_thread_begin.enable
153
144
  end
154
145
 
155
146
  def add_initial_commands cmds
@@ -160,7 +151,11 @@ module DEBUGGER__
160
151
  end
161
152
 
162
153
  def source path
163
- @sr.get(path)
154
+ if CONFIG[:use_colorize]
155
+ @sr.get_colored(path)
156
+ else
157
+ @sr.get(path)
158
+ end
164
159
  end
165
160
 
166
161
  def inspect
@@ -409,17 +404,23 @@ module DEBUGGER__
409
404
 
410
405
  @tc << [:show, :edit, arg]
411
406
 
412
- # * `i[nfo]`
407
+ # * `i[nfo]`, `i[nfo] l[ocal[s]]`
413
408
  # * Show information about the current frame (local variables)
414
409
  # * It includes `self` as `%self` and a return value as `%return`.
415
- # * `i[nfo] <expr>`
416
- # * Show information about the result of <expr>.
410
+ # * `i[nfo] th[read[s]]
411
+ # * Show all threads (same as `th[read]`).
417
412
  when 'i', 'info'
418
413
  case arg
419
414
  when nil
420
415
  @tc << [:show, :local]
416
+ when 'l', /locals?/
417
+ @tc << [:show, :local]
418
+ when 'th', /threads?/
419
+ thread_list
420
+ return :retry
421
421
  else
422
- @tc << [:show, :object_info, arg]
422
+ show_help 'info'
423
+ return :retry
423
424
  end
424
425
 
425
426
  # * `display`
@@ -855,7 +856,7 @@ module DEBUGGER__
855
856
 
856
857
  def add_catch_breakpoint arg
857
858
  bp = CatchBreakpoint.new(arg)
858
- add_braekpoint bp
859
+ add_breakpoint bp
859
860
  end
860
861
 
861
862
  def add_check_breakpoint expr
@@ -897,26 +898,37 @@ module DEBUGGER__
897
898
  b = tp.binding
898
899
  if var_name = b.local_variables.first
899
900
  mid = b.local_variable_get(var_name)
900
- found = false
901
+ unresolved = false
901
902
 
902
903
  @bps.each{|k, bp|
903
904
  case bp
904
905
  when MethodBreakpoint
905
906
  if bp.method.nil?
906
- found = true
907
907
  if bp.sig_method_name == mid.to_s
908
- bp.enable(quiet: true)
908
+ bp.try_enable(quiet: true)
909
909
  end
910
910
  end
911
+
912
+ unresolved = true unless bp.enabled?
911
913
  end
912
914
  }
913
- unless found
915
+ unless unresolved
914
916
  METHOD_ADDED_TRACKER.disable
915
917
  end
916
918
  end
917
919
  end
918
920
  end
919
921
 
922
+ # manual configuration methods
923
+
924
+ def self.add_line_breakpoint file, line, **kw
925
+ ::DEBUGGER__::SESSION.add_line_breakpoint file, line, **kw
926
+ end
927
+
928
+ def self.add_catch_breakpoint pat
929
+ ::DEBUGGER__::SESSION.add_catch_breakpoint pat
930
+ end
931
+
920
932
  # String for requring location
921
933
  # nil for -r
922
934
  def self.require_location
@@ -934,7 +946,12 @@ module DEBUGGER__
934
946
  nil
935
947
  end
936
948
 
937
- def self.console
949
+ # start methods
950
+
951
+ def self.console **kw
952
+ set_config(kw)
953
+
954
+ require_relative 'console'
938
955
  initialize_session UI_Console.new
939
956
 
940
957
  @prev_handler = trap(:SIGINT){
@@ -942,14 +959,30 @@ module DEBUGGER__
942
959
  }
943
960
  end
944
961
 
945
- def self.add_line_breakpoint file, line, **kw
946
- ::DEBUGGER__::SESSION.add_line_breakpoint file, line, **kw
962
+ def self.open host: nil, port: ::DEBUGGER__::CONFIG[:port], sock_path: nil, sock_dir: nil, **kw
963
+ set_config(kw)
964
+
965
+ if port
966
+ open_tcp host: host, port: port
967
+ else
968
+ open_unix sock_path: sock_path, sock_dir: sock_dir
969
+ end
947
970
  end
948
971
 
949
- def self.add_catch_breakpoint pat
950
- ::DEBUGGER__::SESSION.add_catch_breakpoint pat
972
+ def self.open_tcp host: nil, port:, **kw
973
+ set_config(kw)
974
+ require_relative 'server'
975
+ initialize_session UI_TcpServer.new(host: host, port: port)
951
976
  end
952
977
 
978
+ def self.open_unix sock_path: nil, sock_dir: nil, **kw
979
+ set_config(kw)
980
+ require_relative 'server'
981
+ initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
982
+ end
983
+
984
+ # boot utilities
985
+
953
986
  class << self
954
987
  define_method :initialize_session do |ui|
955
988
  ::DEBUGGER__.const_set(:SESSION, Session.new(ui))
@@ -963,7 +996,7 @@ module DEBUGGER__
963
996
  def bp; nil; end
964
997
  end
965
998
 
966
- if ::DEBUGGER__::CONFIG[:nonstop] != '1'
999
+ if !::DEBUGGER__::CONFIG[:nonstop]
967
1000
  if loc = ::DEBUGGER__.require_location
968
1001
  # require 'debug/console' or 'debug'
969
1002
  add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
@@ -1052,8 +1085,6 @@ module DEBUGGER__
1052
1085
  r.join("\n")
1053
1086
  end
1054
1087
 
1055
- CONFIG = ::DEBUGGER__.parse_argv(ENV['RUBY_DEBUG_OPT'])
1056
-
1057
1088
  class ::Module
1058
1089
  def method_added mid; end
1059
1090
  def singleton_method_added mid; end
@@ -1,27 +1,57 @@
1
+ require 'irb/color' # IRB::Color.colorize_code
2
+
1
3
  module DEBUGGER__
2
4
  class SourceRepository
3
5
  def initialize
4
6
  @files = {} # filename => [src, iseq]
7
+ @color_files = {}
5
8
  end
6
9
 
7
10
  def add iseq, src
8
11
  path = iseq.absolute_path
9
12
  path = '-e' if iseq.path == '-e'
13
+ add_path path, src: src
14
+ end
10
15
 
16
+ def add_path path, src: nil
11
17
  case
12
- when path = iseq.absolute_path
13
- src = File.read(path)
14
- when iseq.path == '-e'
15
- path = '-e'
18
+ when src
19
+ if File.file?(path)
20
+ path = '(eval)' + path
21
+ src = nil
22
+ end
23
+ when path == '-e'
24
+ when path
25
+ begin
26
+ src = File.read(path)
27
+ rescue SystemCallError
28
+ end
16
29
  else
17
30
  src = nil
18
31
  end
19
32
 
20
- @files[path] = src.lines if src
33
+ if src
34
+ src = src.gsub("\r\n", "\n") # CRLF -> LF
35
+ @files[path] = src.lines
36
+ end
21
37
  end
22
38
 
23
39
  def get path
24
- @files[path]
40
+ if @files.has_key? path
41
+ @files[path]
42
+ else
43
+ add_path path
44
+ end
45
+ end
46
+
47
+ def get_colored path
48
+ if src_lines = @color_files[path]
49
+ return src_lines
50
+ else
51
+ if src_lines = get(path)
52
+ @color_files[path] = IRB::Color.colorize_code(src_lines.join).lines
53
+ end
54
+ end
25
55
  end
26
56
  end
27
57
  end
@@ -1,5 +1,7 @@
1
1
  require 'objspace'
2
2
  require 'pp'
3
+ require 'irb/color'
4
+ require_relative 'frame_info'
3
5
 
4
6
  module DEBUGGER__
5
7
  class ThreadClient
@@ -12,6 +14,27 @@ module DEBUGGER__
12
14
 
13
15
  attr_reader :location, :thread, :mode, :id
14
16
 
17
+ def colorize str, color
18
+ if CONFIG[:use_colorize]
19
+ IRB::Color.colorize str, color
20
+ else
21
+ str
22
+ end
23
+ end
24
+
25
+ def default_frame_formatter frame
26
+ call_identifier_str = colorize(frame.call_identifier_str, [:BLUE, :BOLD])
27
+ location_str = colorize(frame.location_str, [:YELLOW])
28
+ result = "#{call_identifier_str} at #{location_str}"
29
+
30
+ if return_str = frame.return_str
31
+ return_str = colorize(frame.return_str, [:MAGENTA])
32
+ result += " #=> #{return_str}"
33
+ end
34
+
35
+ result
36
+ end
37
+
15
38
  def initialize id, q_evt, q_cmd, thr = Thread.current
16
39
  @id = id
17
40
  @thread = thr
@@ -19,8 +42,9 @@ module DEBUGGER__
19
42
  @q_cmd = q_cmd
20
43
  @step_tp = nil
21
44
  @output = []
22
- @src_lines_on_stop = (DEBUGGER__::CONFIG[:show_src_lines] || 10).to_i
23
- @show_frames_on_stop = (DEBUGGER__::CONFIG[:show_frames] || 2).to_i
45
+ @src_lines_on_stop = (::DEBUGGER__::CONFIG[:show_src_lines] || 10).to_i
46
+ @show_frames_on_stop = (::DEBUGGER__::CONFIG[:show_frames] || 2).to_i
47
+ @frame_formatter = method(:default_frame_formatter)
24
48
  set_mode nil
25
49
  end
26
50
 
@@ -129,7 +153,7 @@ module DEBUGGER__
129
153
  next if SESSION.break? tp.path, tp.lineno
130
154
  next if !yield
131
155
  next if tp.path.start_with?(__dir__)
132
- next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
156
+ next unless File.exist?(tp.path) if ::DEBUGGER__::CONFIG[:skip_nosrc]
133
157
 
134
158
  tp.disable
135
159
  on_suspend tp.event, tp
@@ -140,7 +164,7 @@ module DEBUGGER__
140
164
  next if thread != Thread.current
141
165
  next if SESSION.break? tp.path, tp.lineno
142
166
  next if !yield
143
- next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
167
+ next unless File.exist?(tp.path) if ::DEBUGGER__::CONFIG[:skip_nosrc]
144
168
 
145
169
  tp.disable
146
170
  on_suspend tp.event, tp
@@ -157,23 +181,15 @@ module DEBUGGER__
157
181
  end
158
182
  end
159
183
 
160
- def file_lines path
161
- if (src_lines = SESSION.source(path))
162
- src_lines
163
- elsif File.exist?(path)
164
- File.readlines(path)
165
- end
166
- end
167
-
168
184
  def show_src(frame_index: @current_frame_index,
169
185
  update_line: false,
170
186
  max_lines: 10,
171
187
  start_line: nil,
172
188
  end_line: nil,
173
189
  dir: +1)
174
- #
190
+
175
191
  if @target_frames && frame = @target_frames[frame_index]
176
- if file_lines = file_lines(path = frame.location.path)
192
+ if file_lines = frame.file_lines
177
193
  frame_line = frame.location.lineno - 1
178
194
 
179
195
  lines = file_lines.map.with_index do |e, i|
@@ -206,11 +222,11 @@ module DEBUGGER__
206
222
  end
207
223
 
208
224
  if start_line != end_line && max_lines
209
- puts "[#{start_line+1}, #{end_line}] in #{pretty_path(path)}" if !update_line && max_lines != 1
225
+ puts "[#{start_line+1}, #{end_line}] in #{frame.pretty_path}" if !update_line && max_lines != 1
210
226
  puts lines[start_line ... end_line]
211
227
  end
212
228
  else # no file lines
213
- puts "# No sourcefile available for #{path}"
229
+ puts "# No sourcefile available for #{frame.path}"
214
230
  end
215
231
  end
216
232
  end
@@ -218,7 +234,7 @@ module DEBUGGER__
218
234
  def show_by_editor path = nil
219
235
  unless path
220
236
  if @target_frames && frame = @target_frames[@current_frame_index]
221
- path = frame.location.path
237
+ path = frame.path
222
238
  else
223
239
  return # can't get path
224
240
  end
@@ -287,101 +303,12 @@ module DEBUGGER__
287
303
  end
288
304
  end
289
305
 
290
- def parameters_info b, vars
291
- vars.map{|var|
292
- begin
293
- "#{var}=#{short_inspect(b.local_variable_get(var))}"
294
- rescue NameError, TypeError
295
- nil
296
- end
297
- }.compact.join(', ')
298
- end
299
-
300
- def get_singleton_class obj
301
- obj.singleton_class # TODO: don't use it
302
- rescue TypeError
303
- nil
304
- end
305
-
306
- def klass_sig frame
307
- klass = frame.class
308
- if klass == get_singleton_class(frame.self)
309
- "#{frame.self}."
310
- else
311
- "#{frame.class}#"
312
- end
313
- end
314
-
315
- SHORT_INSPECT_LENGTH = 40
316
-
317
- def short_inspect obj
318
- str = obj.inspect
319
- if str.length > SHORT_INSPECT_LENGTH
320
- str[0...SHORT_INSPECT_LENGTH] + '...'
321
- else
322
- str
323
- end
324
- end
325
-
326
- HOME = ENV['HOME'] ? (ENV['HOME'] + '/') : nil
327
-
328
- def pretty_path path
329
- use_short_path = CONFIG[:use_short_path]
330
-
331
- case
332
- when use_short_path && path.start_with?(dir = RbConfig::CONFIG["rubylibdir"] + '/')
333
- path.sub(dir, '$(rubylibdir)/')
334
- when use_short_path && Gem.path.any? do |gp|
335
- path.start_with?(dir = gp + '/gems/')
336
- end
337
- path.sub(dir, '$(Gem)/')
338
- when HOME && path.start_with?(HOME)
339
- path.sub(HOME, '~/')
340
- else
341
- path
342
- end
343
- end
344
-
345
- def pretty_location loc
346
- " at #{pretty_path(loc.path)}:#{loc.lineno}"
347
- end
348
-
349
- def frame_str i
350
- frame = @target_frames[i]
351
- b = frame.binding
352
-
306
+ def frame_str(i)
353
307
  cur_str = (@current_frame_index == i ? '=>' : ' ')
354
-
355
- if b && (iseq = frame.iseq)
356
- if iseq.type == :block
357
- if (argc = iseq.argc) > 0
358
- args = parameters_info b, iseq.locals[0...argc]
359
- args_str = "{|#{args}|}"
360
- end
361
-
362
- label_prefix = frame.location.label.sub('block'){ "block#{args_str}" }
363
- ci_str = label_prefix
364
- elsif (callee = b.eval('__callee__', __FILE__, __LINE__)) && (argc = iseq.argc) > 0
365
- args = parameters_info b, iseq.locals[0...argc]
366
- ksig = klass_sig frame
367
- ci_str = "#{ksig}#{callee}(#{args})"
368
- else
369
- ci_str = frame.location.label
370
- end
371
-
372
- loc_str = "#{pretty_location(frame.location)}"
373
-
374
- if frame.has_return_value
375
- return_str = " #=> #{short_inspect(frame.return_value)}"
376
- end
377
- else
378
- ksig = klass_sig frame
379
- callee = frame.location.base_label
380
- ci_str = "[C] #{ksig}#{callee}"
381
- loc_str = "#{pretty_location(frame.location)}"
382
- end
383
-
384
- "#{cur_str}##{i}\t#{ci_str}#{loc_str}#{return_str}"
308
+ prefix = "#{cur_str}##{i}"
309
+ frame = @target_frames[i]
310
+ frame_string = @frame_formatter.call(frame)
311
+ "#{prefix}\t#{frame_string}"
385
312
  end
386
313
 
387
314
  def show_frames max = (@target_frames || []).size
@@ -457,7 +384,7 @@ module DEBUGGER__
457
384
  step_tp{true}
458
385
  when :next
459
386
  frame = @target_frames.first
460
- path = frame.location.absolute_path || "!eval:#{frame.location.path}"
387
+ path = frame.location.absolute_path || "!eval:#{frame.path}"
461
388
  line = frame.location.lineno
462
389
  frame.iseq.traceable_lines_norec(lines = {})
463
390
  next_line = lines.keys.bsearch{|e| e > line}
@@ -533,7 +460,7 @@ module DEBUGGER__
533
460
  case type
534
461
  when :up
535
462
  if @current_frame_index + 1 < @target_frames.size
536
- @current_frame_index += 1
463
+ @current_frame_index += 1
537
464
  show_src max_lines: 1
538
465
  show_frame(@current_frame_index)
539
466
  end
data/lib/debug/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module DEBUGGER__
2
- VERSION = "1.0.0.beta3"
2
+ VERSION = "1.0.0.beta4"
3
3
  end
data/misc/README.md.erb CHANGED
@@ -62,13 +62,14 @@ $ ruby -r debug/run target.rb
62
62
 
63
63
  $ cat target.rb
64
64
  require 'debug/run' # start the debug console
65
- ...
65
+ ... rest of program ...
66
66
 
67
67
  # or
68
68
 
69
69
  $ cat target.rb
70
70
  require 'debug/session' # introduce the functionality
71
71
  DEBUGGER__.console # and start the debug console
72
+ ... rest of program ...
72
73
 
73
74
  $ ruby target.rb
74
75
  ```
@@ -295,10 +296,10 @@ You can control debuggee's behavior with environment variables:
295
296
  * `RUBY_DEBUG_COMMANDS`: Debug commands invoked at the first stop. Commands should be separated by ';;'.
296
297
  * `RUBY_DEBUG_SHOW_SRC_LINES`: Show n lines source code on breakpoint (default: 10 lines).
297
298
  * `RUBY_DEBUG_SHOW_FRAMES`: Show n frames on breakpoint (default: 2 frames).
298
-
299
299
  * Remote debugging
300
300
  * `RUBY_DEBUG_PORT`: TCP/IP remote debugging: port to open.
301
301
  * `RUBY_DEBUG_HOST`: TCP/IP remote debugging: host (localhost if not given) to open.
302
+ * `RUBY_DEBUG_SOCK_PATH`: UNIX Domain Socket remote debugging: socket path to open.
302
303
  * `RUBY_DEBUG_SOCK_DIR`: UNIX Domain Socket remote debugging: socket directory to open.
303
304
 
304
305
  ## Debug command on the debug console
@@ -322,3 +323,5 @@ The `<...>` notation means the argument.
322
323
  # Contributing
323
324
 
324
325
  Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/debug.
326
+
327
+ Please also check the [contributing guideline](/CONTRIBUTING.md).
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: debug
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta3
4
+ version: 1.0.0.beta4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichi Sasada
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-14 00:00:00.000000000 Z
11
+ date: 2021-05-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Debugging functionality for Ruby. This is completely rewritten debug.rb
14
14
  which was contained by the encient Ruby versions.
@@ -21,10 +21,12 @@ extensions:
21
21
  extra_rdoc_files: []
22
22
  files:
23
23
  - ".gitignore"
24
+ - CONTRIBUTING.md
24
25
  - Gemfile
25
26
  - LICENSE.txt
26
27
  - README.md
27
28
  - Rakefile
29
+ - TODO.md
28
30
  - bin/console
29
31
  - bin/setup
30
32
  - debug.gemspec
@@ -38,6 +40,7 @@ files:
38
40
  - lib/debug/client.rb
39
41
  - lib/debug/config.rb
40
42
  - lib/debug/console.rb
43
+ - lib/debug/frame_info.rb
41
44
  - lib/debug/open.rb
42
45
  - lib/debug/run.rb
43
46
  - lib/debug/server.rb