debug 0.2.1 → 1.0.0.alpha0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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