debug 0.2.0 → 1.0.0.beta3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/debug/bp.vim ADDED
@@ -0,0 +1,68 @@
1
+ let g:rdb_bps = {}
2
+
3
+ function SET_BP()
4
+ let signed = sign_getplaced(bufname(), {'lnum': line('.')})
5
+ if empty(signed[0]['signs'])
6
+ call sign_place(0, '', 'signBP', bufname(), {'lnum': line('.')})
7
+ else
8
+ "echo signed[0]['signs']
9
+ call sign_unplace('', {'buffer': bufname(), 'id': signed[0]['signs'][0]['id']})
10
+ endif
11
+ endfunction
12
+
13
+ function UPDATE_BPS()
14
+ let signs = sign_getplaced(bufname())
15
+ let key = expand('%:p')
16
+
17
+ if empty(signs[0]['signs'])
18
+ let removed = remove(g:rdb_bps, key)
19
+ else
20
+ let g:rdb_bps[key] = signs[0]['signs']
21
+ endif
22
+ endfunction
23
+
24
+ function APPLY_BPS()
25
+ let key = expand('%:p')
26
+ if has_key(g:rdb_bps, key)
27
+ for b in g:rdb_bps[key]
28
+ call sign_place(0, '', 'signBP', bufname(), {'lnum': b['lnum']})
29
+ endfor
30
+ endif
31
+ endfunction
32
+
33
+ function WRITE_BPS()
34
+ call writefile([json_encode(g:rdb_bps)], '.rdb_breakpoints.json')
35
+ endfunction
36
+
37
+ " load
38
+ try
39
+ let json = readfile('.rdb_breakpoints.json')
40
+ let g:rdb_bps = json_decode(json[0])
41
+ " {"/full/path/to/file1": [{"lnum": 10}, ...], ...}
42
+ catch /Can't open/
43
+ let g:rdb_bps = {}
44
+ catch /Invalid arguments for function json_decode/
45
+ let g:rdb_bps = {}
46
+ endtry
47
+
48
+ sign define signBP text=BR
49
+
50
+ call APPLY_BPS()
51
+
52
+ autocmd BufReadPost * call APPLY_BPS()
53
+ autocmd BufUnload * call UPDATE_BPS()
54
+ autocmd VimLeave * call WRITE_BPS()
55
+
56
+ function! s:ruby_bp_settings() abort
57
+ echomsg "Type <Space> to toggle break points and <q> to quit"
58
+
59
+ if &readonly
60
+ nnoremap <silent> <buffer> <Space> :call SET_BP()<CR>
61
+ nnoremap <silent> <buffer> q :<C-u>quit<CR>
62
+ endif
63
+ endfunction
64
+
65
+ " autocmd FileType ruby call s:ruby_bp_settings()
66
+ autocmd BufEnter *.rb call s:ruby_bp_settings()
67
+
68
+ call s:ruby_bp_settings()
@@ -0,0 +1,374 @@
1
+ module DEBUGGER__
2
+ class Breakpoint
3
+ attr_reader :key
4
+
5
+ def initialize do_enable = true
6
+ @deleted = false
7
+
8
+ setup
9
+ enable if do_enable
10
+ end
11
+
12
+ def safe_eval b, expr
13
+ b.eval(expr)
14
+ rescue Exception => e
15
+ puts "[EVAL ERROR]"
16
+ puts " expr: #{expr}"
17
+ puts " err: #{e} (#{e.class})"
18
+ puts "Error caused by #{self}."
19
+ nil
20
+ end
21
+
22
+ def setup
23
+ raise "not implemented..."
24
+ end
25
+
26
+ def enable
27
+ @tp.enable
28
+ end
29
+
30
+ def disable
31
+ @tp&.disable
32
+ end
33
+
34
+ def enabled?
35
+ @tp.enabled?
36
+ end
37
+
38
+ def delete
39
+ disable
40
+ @deleted = true
41
+ end
42
+
43
+ def deleted?
44
+ @deleted
45
+ end
46
+
47
+ def suspend
48
+ ThreadClient.current.on_breakpoint @tp, self
49
+ end
50
+
51
+ def to_s
52
+ if @cond
53
+ " if #{@cond}"
54
+ else
55
+ ""
56
+ end
57
+ end
58
+ end
59
+
60
+ class LineBreakpoint < Breakpoint
61
+ attr_reader :path, :line, :iseq
62
+
63
+ def initialize path, line, cond: nil, oneshot: false, hook_call: true
64
+ @path = path
65
+ @line = line
66
+ @cond = cond
67
+ @oneshot = oneshot
68
+ @hook_call = hook_call
69
+
70
+ @iseq = nil
71
+ @type = nil
72
+
73
+ @key = [@path, @line].freeze
74
+
75
+ super()
76
+ try_activate
77
+ end
78
+
79
+ def setup
80
+ return unless @type
81
+
82
+ if !@cond
83
+ @tp = TracePoint.new(@type) do |tp|
84
+ delete if @oneshot
85
+ suspend
86
+ end
87
+ else
88
+ @tp = TracePoint.new(@type) do |tp|
89
+ next unless safe_eval tp.binding, @cond
90
+ delete if @oneshot
91
+ suspend
92
+ end
93
+ end
94
+ end
95
+
96
+ def enable
97
+ return unless @iseq
98
+
99
+ if @type == :line
100
+ @tp.enable(target: @iseq, target_line: @line)
101
+ else
102
+ @tp.enable(target: @iseq)
103
+ end
104
+
105
+ rescue ArgumentError
106
+ puts @iseq.disasm # for debug
107
+ raise
108
+ end
109
+
110
+ def activate iseq, event, line
111
+ @iseq = iseq
112
+ @type = event
113
+ @line = line
114
+ @path = iseq.absolute_path
115
+
116
+ @key = [@path, @line].freeze
117
+ SESSION.rehash_bps
118
+
119
+ setup
120
+ enable
121
+ end
122
+
123
+ def activate_exact iseq, events, line
124
+ case
125
+ when events.include?(:RUBY_EVENT_CALL)
126
+ # "def foo" line set bp on the beginning of method foo
127
+ activate(iseq, :call, line)
128
+ when events.include?(:RUBY_EVENT_LINE)
129
+ activate(iseq, :line, line)
130
+ when events.include?(:RUBY_EVENT_RETURN)
131
+ activate(iseq, :return, line)
132
+ when events.include?(:RUBY_EVENT_B_RETURN)
133
+ activate(iseq, :b_return, line)
134
+ when events.include?(:RUBY_EVENT_END)
135
+ activate(iseq, :end, line)
136
+ else
137
+ # not actiavated
138
+ end
139
+ end
140
+
141
+ NearestISeq = Struct.new(:iseq, :line, :events)
142
+
143
+ def try_activate
144
+ nearest = nil # NearestISeq
145
+
146
+ ObjectSpace.each_iseq{|iseq|
147
+ if (iseq.absolute_path || iseq.path) == self.path &&
148
+ iseq.first_lineno <= self.line &&
149
+ iseq.type != :ensure # ensure iseq is copied (duplicated)
150
+
151
+ iseq.traceable_lines_norec(line_events = {})
152
+ lines = line_events.keys.sort
153
+
154
+ if !lines.empty? && lines.last >= line
155
+ nline = lines.bsearch{|l| line <= l}
156
+ events = line_events[nline]
157
+
158
+ next if events == [:RUBY_EVENT_B_CALL]
159
+
160
+ if @hook_call &&
161
+ events.include?(:RUBY_EVENT_CALL) &&
162
+ self.line == iseq.first_lineno
163
+ nline = iseq.first_lineno
164
+ end
165
+
166
+ if !nearest || ((line - nline).abs < (line - nearest.line).abs)
167
+ nearest = NearestISeq.new(iseq, nline, events)
168
+ else
169
+ if @hook_call && nearest.iseq.first_lineno <= iseq.first_lineno
170
+ if (nearest.line > line && !nearest.events.include?(:RUBY_EVENT_CALL)) ||
171
+ (events.include?(:RUBY_EVENT_CALL))
172
+ nearest = NearestISeq.new(iseq, nline, events)
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ }
179
+
180
+ if nearest
181
+ activate_exact nearest.iseq, nearest.events, nearest.line
182
+ end
183
+ end
184
+
185
+ def to_s
186
+ oneshot = @oneshot ? " (oneshot)" : ""
187
+
188
+ if @iseq
189
+ "line bp #{@path}:#{@line} (#{@type})#{oneshot}" + super
190
+ else
191
+ "line bp (pending) #{@path}:#{@line}#{oneshot}" + super
192
+ end
193
+ end
194
+
195
+ def inspect
196
+ "<#{self.class.name} #{self.to_s}>"
197
+ end
198
+ end
199
+
200
+ class CatchBreakpoint < Breakpoint
201
+ def initialize pat
202
+ @pat = pat.freeze
203
+ @key = [:catch, @pat].freeze
204
+
205
+ super()
206
+ end
207
+
208
+ def setup
209
+ @tp = TracePoint.new(:raise){|tp|
210
+ exc = tp.raised_exception
211
+ exc.class.ancestors.each{|cls|
212
+ suspend if pat === cls.name
213
+ }
214
+ }
215
+ end
216
+
217
+ def to_s
218
+ "catch bp #{@pat.inspect}"
219
+ end
220
+ end
221
+
222
+ class CheckBreakpoint < Breakpoint
223
+ def initialize expr
224
+ @expr = expr.freeze
225
+ @key = [:check, @expr].freeze
226
+
227
+ super()
228
+ end
229
+
230
+ def setup
231
+ @tp = TracePoint.new(:line){|tp|
232
+ next if tp.path.start_with? __dir__
233
+ next if tp.path.start_with? '<internal:'
234
+
235
+ if safe_eval tp.binding, @expr
236
+ suspend
237
+ end
238
+ }
239
+ end
240
+
241
+ def to_s
242
+ "check bp: #{@expr}"
243
+ end
244
+ end
245
+
246
+ class WatchExprBreakpoint < Breakpoint
247
+ def initialize expr, current
248
+ @expr = expr.freeze
249
+ @key = [:watch, @expr].freeze
250
+
251
+ @current = current
252
+ super()
253
+ end
254
+
255
+ def watch_eval b
256
+ result = b.eval(@expr)
257
+ if result != @current
258
+ begin
259
+ @prev = @current
260
+ @current = result
261
+ suspend
262
+ ensure
263
+ remove_instance_variable(:@prev)
264
+ end
265
+ end
266
+ rescue Exception => e
267
+ false
268
+ end
269
+
270
+ def setup
271
+ @tp = TracePoint.new(:line, :return, :b_return){|tp|
272
+ next if tp.path.start_with? __dir__
273
+ next if tp.path.start_with? '<internal:'
274
+
275
+ watch_eval(tp.binding)
276
+ }
277
+ end
278
+
279
+ def to_s
280
+ if defined? @prev
281
+ "watch bp: #{@expr} = #{@prev} -> #{@current}"
282
+ else
283
+ "watch bp: #{@expr} = #{@current}"
284
+ end
285
+ end
286
+ end
287
+
288
+ class MethodBreakpoint < Breakpoint
289
+ attr_reader :sig_method_name, :method
290
+
291
+ def initialize b, klass_name, op, method_name, cond
292
+ @sig_klass_name = klass_name
293
+ @sig_op = op
294
+ @sig_method_name = method_name
295
+ @klass_eval_binding = b
296
+
297
+ @klass = nil
298
+ @method = nil
299
+ @cond = cond
300
+ @key = "#{klass_name}#{op}#{method_name}".freeze
301
+
302
+ super(false)
303
+ end
304
+
305
+ def setup
306
+ if @cond
307
+ @tp = TracePoint.new(:call){|tp|
308
+ next unless safe_eval tp.binding, @cond
309
+ suspend
310
+ }
311
+ else
312
+ @tp = TracePoint.new(:call){|tp|
313
+ suspend
314
+ }
315
+ end
316
+ end
317
+
318
+ def eval_class_name
319
+ return @klass if @klass
320
+ @klass = @klass_eval_binding.eval(@sig_klass_name)
321
+ @klass_eval_binding = nil
322
+ @klass
323
+ end
324
+
325
+ def search_method
326
+ case @sig_op
327
+ when '.'
328
+ @method = @klass.method(@sig_method_name)
329
+ when '#'
330
+ @method = @klass.instance_method(@sig_method_name)
331
+ else
332
+ raise "Unknown op: #{@sig_op}"
333
+ end
334
+ end
335
+
336
+ def enable quiet: false
337
+ eval_class_name
338
+ search_method
339
+
340
+ begin
341
+ retried = false
342
+ @tp.enable(target: @method)
343
+
344
+ rescue ArgumentError => e
345
+ raise if retried
346
+ retried = true
347
+
348
+ # maybe C method
349
+ @klass.module_eval do
350
+ orig_name = @sig_method_name + '__orig__'
351
+ alias_method orig_name, @sig_method_name
352
+ define_method(@sig_method_name) do |*args|
353
+ send(orig_name, *args)
354
+ end
355
+ end
356
+ retry
357
+ end
358
+ rescue Exception
359
+ raise unless quiet
360
+ end
361
+
362
+ def sig
363
+ @key
364
+ end
365
+
366
+ def to_s
367
+ if @method
368
+ "method bp: #{sig}"
369
+ else
370
+ "method bp (pending): #{sig}"
371
+ end + super
372
+ end
373
+ end
374
+ end
@@ -0,0 +1,128 @@
1
+ require 'socket'
2
+ require_relative 'config'
3
+
4
+ module DEBUGGER__
5
+ class CommandLineOptionError < Exception; end
6
+
7
+ class Client
8
+ begin
9
+ require 'readline'
10
+ def readline
11
+ Readline.readline("\n(rdb) ", true)
12
+ end
13
+ rescue LoadError
14
+ def readline
15
+ print "\n(rdb) "
16
+ gets
17
+ end
18
+ end
19
+
20
+ def initialize argv
21
+ case argv.size
22
+ when 0
23
+ connect_unix
24
+ when 1
25
+ case arg = argv.shift
26
+ when /-h/, /--help/
27
+ help
28
+ exit
29
+ when /\A\d+\z/
30
+ connect_tcp nil, arg.to_i
31
+ else
32
+ connect_unix arg
33
+ end
34
+ when 2
35
+ connect_tcp argv[0], argv[1]
36
+ else
37
+ raise CommandLineOptionError
38
+ end
39
+ end
40
+
41
+ def cleanup_unix_domain_sockets
42
+ Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*') do |file|
43
+ if /(\d+)$/ =~ file
44
+ begin
45
+ Process.kill(0, $1.to_i)
46
+ rescue Errno::ESRCH
47
+ File.unlink(file)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ def connect_unix name = nil
54
+ if name
55
+ if File.exist? name
56
+ @s = Socket.unix(name)
57
+ else
58
+ @s = Socket.unix(File.join(DEBUGGER__.unix_domain_socket_basedir, name))
59
+ end
60
+ else
61
+ cleanup_unix_domain_sockets
62
+ files = Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*')
63
+ case files.size
64
+ when 0
65
+ $stderr.puts "No debug session is available."
66
+ exit
67
+ when 1
68
+ @s = Socket.unix(files.first)
69
+ else
70
+ $stderr.puts "Please select a debug session:"
71
+ files.each{|f|
72
+ $stderr.puts " #{File.basename(f)}"
73
+ }
74
+ exit
75
+ end
76
+ end
77
+ end
78
+
79
+ def connect_tcp host, port
80
+ @s = Socket.tcp(host, port)
81
+ end
82
+
83
+ def connect
84
+ trap(:SIGINT){
85
+ @s.puts "pause"
86
+ }
87
+
88
+ while line = @s.gets
89
+ # p line: line
90
+ case line
91
+ when /^out (.*)/
92
+ puts "#{$1}"
93
+ when /^input/
94
+ prev_trap = trap(:SIGINT, 'DEFAULT')
95
+
96
+ begin
97
+ line = readline
98
+ rescue Interrupt
99
+ retry
100
+ ensure
101
+ trap(:SIGINT, prev_trap)
102
+ end
103
+
104
+ line = (line || 'quit').strip
105
+ @s.puts "command #{line}"
106
+ when /^ask (.*)/
107
+ print $1
108
+ @s.puts "answer #{gets || ''}"
109
+ when /^quit/
110
+ raise 'quit'
111
+ else
112
+ puts "(unknown) #{line.inspect}"
113
+ end
114
+ end
115
+ rescue
116
+ STDERR.puts "disconnected (#{$!})"
117
+ exit
118
+ end
119
+ end
120
+ end
121
+
122
+ def connect argv = ARGV
123
+ DEBUGGER__::Client.new(argv).connect
124
+ end
125
+
126
+ if __FILE__ == $0
127
+ connect
128
+ end