debug 0.2.1 → 1.0.0.alpha0

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.
@@ -0,0 +1,402 @@
1
+ module DEBUGGER__
2
+ class ThreadClient
3
+ def self.current
4
+ Thread.current[:DEBUGGER__ThreadClient] || begin
5
+ tc = SESSION.thread_client
6
+ Thread.current[:DEBUGGER__ThreadClient] = tc
7
+ end
8
+ end
9
+
10
+ attr_reader :location, :thread
11
+
12
+ def initialize q_evt, q_cmd, thr = Thread.current
13
+ @thread = thr
14
+ @q_evt = q_evt
15
+ @q_cmd = q_cmd
16
+ @step_tp = nil
17
+ @output = []
18
+ @mode = nil
19
+ end
20
+
21
+ def inspect
22
+ "#<DEBUGGER__ThreadClient #{@thread}>"
23
+ end
24
+
25
+ def puts str = ''
26
+ case str
27
+ when nil
28
+ @output << "\n"
29
+ when Array
30
+ str.each{|s| puts s}
31
+ else
32
+ @output << str.chomp + "\n"
33
+ end
34
+ end
35
+
36
+ def << req
37
+ @q_cmd << req
38
+ end
39
+
40
+ def event! ev, *args
41
+ @q_evt << [self, @output, ev, *args]
42
+ @output = []
43
+ end
44
+
45
+ ## events
46
+
47
+ def on_trap
48
+ if @mode == :wait_next_action
49
+ raise Interrupt
50
+ else
51
+ on_suspend :trap
52
+ end
53
+ end
54
+
55
+ def on_pause
56
+ on_suspend :pause
57
+ end
58
+
59
+ def on_load iseq, eval_src
60
+ event! :load, iseq, eval_src
61
+ wait_next_action
62
+ end
63
+
64
+ def on_breakpoint tp
65
+ on_suspend tp.event, tp
66
+ end
67
+
68
+ def on_suspend event, tp = nil
69
+ @current_frame_index = 0
70
+ @target_frames = target_frames
71
+ cf = @target_frames.first
72
+ if cf
73
+ @location = cf.location
74
+ case event
75
+ when :return, :b_return, :c_return
76
+ cf.has_return_value = true
77
+ cf.return_value = tp.return_value
78
+ end
79
+ end
80
+
81
+ if event != :pause
82
+ show_src
83
+ print_frames 3
84
+ event! :suspend, :breakpoint
85
+ end
86
+
87
+ wait_next_action
88
+ end
89
+
90
+ ## control all
91
+
92
+ def step_tp
93
+ @step_tp.disable if @step_tp
94
+ @step_tp = TracePoint.new(:line, :b_return, :return){|tp|
95
+ next if SESSION.break? tp.path, tp.lineno
96
+ next if !yield
97
+ tp.disable
98
+ on_suspend tp.event, tp
99
+ }
100
+
101
+ @step_tp.enable(target_thread: Thread.current)
102
+ end
103
+
104
+ FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :has_return_value, :return_value)
105
+
106
+ def target_frames
107
+ RubyVM::DebugInspector.open{|dc|
108
+ locs = dc.backtrace_locations
109
+ locs.map.with_index{|e, i|
110
+ unless File.dirname(e.path) == File.dirname(__FILE__)
111
+ FrameInfo.new(
112
+ e,
113
+ dc.frame_self(i),
114
+ dc.frame_binding(i),
115
+ dc.frame_iseq(i),
116
+ dc.frame_class(i))
117
+ end
118
+ }.compact
119
+ }
120
+ end
121
+
122
+ def target_frames_count
123
+ RubyVM::DebugInspector.open{|dc|
124
+ locs = dc.backtrace_locations
125
+ locs.count{|e|
126
+ e.path != __FILE__
127
+ }
128
+ }
129
+ end
130
+
131
+ def current_frame
132
+ @target_frames[@current_frame_index]
133
+ end
134
+
135
+ def file_lines path
136
+ if (src = SESSION.source(path)) && src[0]
137
+ src[0].lines
138
+ elsif File.exist?(path)
139
+ File.readlines(path)
140
+ end
141
+ end
142
+
143
+ def show_src frame_index = @current_frame_index, max_lines: 10
144
+ if current_line = @target_frames[frame_index]&.location
145
+ puts
146
+ path, line = current_line.path, current_line.lineno - 1
147
+ if file_lines = file_lines(path)
148
+ lines = file_lines.map.with_index{|e, i|
149
+ if i == line
150
+ "=> #{'%4d' % (i+1)}| #{e}"
151
+ else
152
+ " #{'%4d' % (i+1)}| #{e}"
153
+ end
154
+ }
155
+ min = [0, line - max_lines/2].max
156
+ max = [min+max_lines, lines.size].min
157
+ puts "[#{min+1}, #{max}] in #{path}"
158
+ puts lines[min ... max]
159
+ end
160
+ end
161
+ end
162
+
163
+ def show_locals
164
+ if s = current_frame&.self
165
+ puts " %self => #{s}"
166
+ end
167
+ if current_frame&.has_return_value
168
+ puts " %return => #{current_frame.return_value}"
169
+ end
170
+ if b = current_frame&.binding
171
+ b.local_variables.each{|loc|
172
+ puts " #{loc} => #{b.local_variable_get(loc).inspect}"
173
+ }
174
+ end
175
+ end
176
+
177
+ def show_ivars
178
+ if s = current_frame&.self
179
+ puts " self => #{s}"
180
+ s.instance_variables.eaach{|iv|
181
+ puts " #{iv} => #{s.instance_variable_get(iv)}"
182
+ }
183
+ end
184
+ end
185
+
186
+ def frame_eval src, failed_value: nil
187
+ begin
188
+ b = current_frame.binding
189
+ if b
190
+ b.eval(src)
191
+ else
192
+ frame_self = current_frame.self
193
+ frame_self.instance_eval(src)
194
+ # puts "eval is not supported on this frame."
195
+ end
196
+ rescue Exception => e
197
+ return failed_value if failed_value
198
+
199
+ puts "Error: #{e}"
200
+ e.backtrace_locations.each do |loc|
201
+ break if loc.path == __FILE__
202
+ puts " #{loc}"
203
+ end
204
+ nil
205
+ end
206
+ end
207
+
208
+ def parameters_info b, vars
209
+ vars.map{|var|
210
+ "#{var}=#{short_inspect(b.eval(var.to_s))}"
211
+ }.join(', ')
212
+ end
213
+
214
+ def klass_sig frame
215
+ klass = frame.class
216
+ if klass == frame.self.singleton_class
217
+ "#{frame.self}."
218
+ else
219
+ "#{frame.class}#"
220
+ end
221
+ end
222
+
223
+ SHORT_INSPECT_LENGTH = 40
224
+
225
+ def short_inspect obj
226
+ str = obj.inspect
227
+ if str.length > SHORT_INSPECT_LENGTH
228
+ str[0...SHORT_INSPECT_LENGTH] + '...'
229
+ else
230
+ str
231
+ end
232
+ end
233
+
234
+ def frame_str i
235
+ buff = ''.dup
236
+ frame = @target_frames[i]
237
+ b = frame.binding
238
+
239
+ buff << (@current_frame_index == i ? '--> ' : ' ')
240
+ if b
241
+ buff << "##{i}\t#{frame.location}"
242
+ else
243
+ buff << "##{i}\t[C] #{frame.location}"
244
+ end
245
+
246
+ if b && (iseq = frame.iseq)
247
+ if iseq.type == :block
248
+ if (argc = iseq.argc) > 0
249
+ args = parameters_info b, iseq.locals[0...iseq.argc]
250
+ buff << " {|#{args}|}"
251
+ end
252
+ else
253
+ callee = b.eval('__callee__')
254
+ if callee && (m = frame.self.method(callee))
255
+ args = parameters_info b, m.parameters.map{|type, v| v}
256
+ ksig = klass_sig frame
257
+ buff << " #{ksig}#{callee}(#{args})"
258
+ end
259
+ end
260
+
261
+ if frame.has_return_value
262
+ buff << " #=> #{short_inspect(frame.return_value)}"
263
+ end
264
+ else
265
+ # p frame.self
266
+ end
267
+
268
+ buff
269
+ end
270
+
271
+ def show_frame_all
272
+ @target_frames.size.times{|i|
273
+ puts frame_str(i)
274
+ }
275
+ end
276
+
277
+ def print_frame i
278
+ puts frame_str(i)
279
+ end
280
+
281
+ def print_frames n
282
+ size = @target_frames.size
283
+ ([size, n].min).times{|i|
284
+ print_frame i
285
+ }
286
+ if n < size
287
+ puts " # and #{size - n} frames (use `bt' command for all frames)"
288
+ end
289
+ end
290
+
291
+ def wait_next_action
292
+ @mode = :wait_next_action
293
+
294
+ while cmds = @q_cmd.pop
295
+ cmd, *args = *cmds
296
+
297
+ case cmd
298
+ when :continue
299
+ break
300
+ when :step
301
+ step_type = args[0]
302
+ case step_type
303
+ when :in
304
+ step_tp{true}
305
+ when :next
306
+ size = @target_frames.size
307
+ step_tp{
308
+ target_frames_count() <= size
309
+ }
310
+ when :finish
311
+ size = @target_frames.size
312
+ step_tp{target_frames_count() < size}
313
+ else
314
+ raise
315
+ end
316
+ break
317
+ when :eval
318
+ eval_type, eval_src = *args
319
+ result = frame_eval(eval_src)
320
+
321
+ case eval_type
322
+ when :p
323
+ puts "=> " + result.inspect
324
+ when :pp
325
+ puts "=> "
326
+ PP.pp(result, out = ''.dup)
327
+ puts out
328
+ when :call
329
+ result = frame_eval(eval_src)
330
+ when :display
331
+ eval_src.each_with_index{|src, i|
332
+ puts "#{i}: #{src} = #{frame_eval(src, failed_value: :error).inspect}"
333
+ }
334
+ result = :ok
335
+ else
336
+ raise "unknown error option: #{args.inspec}"
337
+ end
338
+ event! :result, result
339
+ when :frame
340
+ type, arg = *args
341
+ case type
342
+ when :up
343
+ if @current_frame_index + 1 < @target_frames.size
344
+ @current_frame_index += 1
345
+ show_src max_lines: 1
346
+ print_frame(@current_frame_index)
347
+ end
348
+ when :down
349
+ if @current_frame_index > 0
350
+ @current_frame_index -= 1
351
+ show_src max_lines: 1
352
+ print_frame(@current_frame_index)
353
+ end
354
+ when :set
355
+ if arg
356
+ index = arg.to_i
357
+ if index >= 0 && index < @target_frames.size
358
+ @current_frame_index = index
359
+ else
360
+ puts "out of frame index: #{index}"
361
+ end
362
+ end
363
+ show_src max_lines: 1
364
+ print_frame(@current_frame_index)
365
+ else
366
+ raise "unsupported frame operation: #{arg.inspect}"
367
+ end
368
+ event! :result, nil
369
+ when :show
370
+ type, = *args
371
+ case type
372
+ when :backtrace
373
+ show_frame_all
374
+ when :list
375
+ show_src
376
+ when :locals
377
+ show_locals
378
+ when :ivars
379
+ show_ivars
380
+ else
381
+ raise "unknown show param: " + args.inspect
382
+ end
383
+ event! :result, nil
384
+ else
385
+ raise [ev, *args].inspect
386
+ end
387
+ end
388
+
389
+ rescue SystemExit
390
+ raise
391
+ rescue Exception => e
392
+ pp [__FILE__, __LINE__, e, e.backtrace]
393
+ raise
394
+ ensure
395
+ @mode = nil
396
+ end
397
+
398
+ def to_s
399
+ "(#{@thread.name || @thread.status})@#{current_frame&.location}"
400
+ end
401
+ end
402
+ end
@@ -0,0 +1,19 @@
1
+ require_relative 'server'
2
+ require_relative 'config'
3
+
4
+ module DEBUGGER__
5
+ class UI_UnixDomainServer < UI_Server
6
+ def accept
7
+ @file = DEBUGGER__.create_unix_domain_socket_name
8
+
9
+ $stderr.puts "Debugger can attach via UNIX domain socket (#{@file})"
10
+ Socket.unix_server_loop @file do |sock, addr|
11
+ @client_addr = addr
12
+ yield sock
13
+ end
14
+ end
15
+ end
16
+
17
+ SESSION = Session.new(s = UI_UnixDomainServer.new)
18
+ SESSION.management_threads << s.reader_thread
19
+ end
@@ -0,0 +1,3 @@
1
+ module DEBUGGER__
2
+ VERSION = "1.0.0.alpha0"
3
+ end