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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +4 -5
- data/LICENSE.txt +0 -0
- data/README.md +442 -27
- data/Rakefile +29 -0
- data/debug.gemspec +10 -5
- data/exe/rdbg +34 -0
- data/ext/debug/debug.c +118 -0
- data/ext/debug/extconf.rb +2 -0
- data/ext/debug/iseq_collector.c +91 -0
- data/lib/debug.rb +1 -1111
- data/lib/debug/bp.vim +68 -0
- data/lib/debug/breakpoint.rb +374 -0
- data/lib/debug/client.rb +128 -0
- data/lib/debug/config.rb +125 -0
- data/lib/debug/console.rb +89 -0
- data/lib/debug/open.rb +10 -0
- data/lib/debug/run.rb +2 -0
- data/lib/debug/server.rb +226 -0
- data/lib/debug/session.rb +1071 -0
- data/lib/debug/source_repository.rb +27 -0
- data/lib/debug/thread_client.rb +618 -0
- data/lib/debug/version.rb +3 -0
- data/misc/README.md.erb +324 -0
- metadata +34 -14
@@ -0,0 +1,27 @@
|
|
1
|
+
module DEBUGGER__
|
2
|
+
class SourceRepository
|
3
|
+
def initialize
|
4
|
+
@files = {} # filename => [src, iseq]
|
5
|
+
end
|
6
|
+
|
7
|
+
def add iseq, src
|
8
|
+
path = iseq.absolute_path
|
9
|
+
path = '-e' if iseq.path == '-e'
|
10
|
+
|
11
|
+
case
|
12
|
+
when path = iseq.absolute_path
|
13
|
+
src = File.read(path)
|
14
|
+
when iseq.path == '-e'
|
15
|
+
path = '-e'
|
16
|
+
else
|
17
|
+
src = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
@files[path] = src.lines if src
|
21
|
+
end
|
22
|
+
|
23
|
+
def get path
|
24
|
+
@files[path]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,618 @@
|
|
1
|
+
require 'objspace'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
module DEBUGGER__
|
5
|
+
class ThreadClient
|
6
|
+
def self.current
|
7
|
+
Thread.current[:DEBUGGER__ThreadClient] || begin
|
8
|
+
tc = ::DEBUGGER__::SESSION.thread_client
|
9
|
+
Thread.current[:DEBUGGER__ThreadClient] = tc
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :location, :thread, :mode, :id
|
14
|
+
|
15
|
+
def initialize id, q_evt, q_cmd, thr = Thread.current
|
16
|
+
@id = id
|
17
|
+
@thread = thr
|
18
|
+
@q_evt = q_evt
|
19
|
+
@q_cmd = q_cmd
|
20
|
+
@step_tp = nil
|
21
|
+
@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
|
24
|
+
set_mode nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def close
|
28
|
+
@q_cmd.close
|
29
|
+
end
|
30
|
+
|
31
|
+
def inspect
|
32
|
+
"#<DBG:TC #{self.id}:#{self.mode}@#{@thread.backtrace[-1]}>"
|
33
|
+
end
|
34
|
+
|
35
|
+
def puts str = ''
|
36
|
+
case str
|
37
|
+
when nil
|
38
|
+
@output << "\n"
|
39
|
+
when Array
|
40
|
+
str.each{|s| puts s}
|
41
|
+
else
|
42
|
+
@output << str.chomp + "\n"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def << req
|
47
|
+
@q_cmd << req
|
48
|
+
end
|
49
|
+
|
50
|
+
def event! ev, *args
|
51
|
+
@q_evt << [self, @output, ev, *args]
|
52
|
+
@output = []
|
53
|
+
end
|
54
|
+
|
55
|
+
## events
|
56
|
+
|
57
|
+
def on_trap sig
|
58
|
+
if self.mode == :wait_next_action
|
59
|
+
# raise Interrupt
|
60
|
+
else
|
61
|
+
on_suspend :trap, sig: sig
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def on_pause
|
66
|
+
on_suspend :pause
|
67
|
+
end
|
68
|
+
|
69
|
+
def on_thread_begin th
|
70
|
+
event! :thread_begin, th
|
71
|
+
wait_next_action
|
72
|
+
end
|
73
|
+
|
74
|
+
def on_load iseq, eval_src
|
75
|
+
event! :load, iseq, eval_src
|
76
|
+
wait_next_action
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_breakpoint tp, bp
|
80
|
+
on_suspend tp.event, tp, bp: bp
|
81
|
+
end
|
82
|
+
|
83
|
+
def on_suspend event, tp = nil, bp: nil, sig: nil
|
84
|
+
@current_frame_index = 0
|
85
|
+
@target_frames = DEBUGGER__.capture_frames __dir__
|
86
|
+
|
87
|
+
cf = @target_frames.first
|
88
|
+
if cf
|
89
|
+
@location = cf.location
|
90
|
+
case event
|
91
|
+
when :return, :b_return, :c_return
|
92
|
+
cf.has_return_value = true
|
93
|
+
cf.return_value = tp.return_value
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if event != :pause
|
98
|
+
show_src max_lines: @src_lines_on_stop
|
99
|
+
show_frames @show_frames_on_stop
|
100
|
+
|
101
|
+
if bp
|
102
|
+
event! :suspend, :breakpoint, bp.key
|
103
|
+
elsif sig
|
104
|
+
event! :suspend, :trap, sig
|
105
|
+
else
|
106
|
+
event! :suspend, event
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
wait_next_action
|
111
|
+
end
|
112
|
+
|
113
|
+
## control all
|
114
|
+
|
115
|
+
begin
|
116
|
+
TracePoint.new(:raise){}.enable(target_thread: Thread.current)
|
117
|
+
SUPPORT_TARGET_THREAD = true
|
118
|
+
rescue ArgumentError
|
119
|
+
SUPPORT_TARGET_THREAD = false
|
120
|
+
end
|
121
|
+
|
122
|
+
def step_tp
|
123
|
+
@step_tp.disable if @step_tp
|
124
|
+
|
125
|
+
thread = Thread.current
|
126
|
+
|
127
|
+
if SUPPORT_TARGET_THREAD
|
128
|
+
@step_tp = TracePoint.new(:line, :b_return, :return){|tp|
|
129
|
+
next if SESSION.break? tp.path, tp.lineno
|
130
|
+
next if !yield
|
131
|
+
next if tp.path.start_with?(__dir__)
|
132
|
+
next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
|
133
|
+
|
134
|
+
tp.disable
|
135
|
+
on_suspend tp.event, tp
|
136
|
+
}
|
137
|
+
@step_tp.enable(target_thread: thread)
|
138
|
+
else
|
139
|
+
@step_tp = TracePoint.new(:line, :b_return, :return){|tp|
|
140
|
+
next if thread != Thread.current
|
141
|
+
next if SESSION.break? tp.path, tp.lineno
|
142
|
+
next if !yield
|
143
|
+
next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
|
144
|
+
|
145
|
+
tp.disable
|
146
|
+
on_suspend tp.event, tp
|
147
|
+
}
|
148
|
+
@step_tp.enable
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def current_frame
|
153
|
+
if @target_frames
|
154
|
+
@target_frames[@current_frame_index]
|
155
|
+
else
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
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
|
+
def show_src(frame_index: @current_frame_index,
|
169
|
+
update_line: false,
|
170
|
+
max_lines: 10,
|
171
|
+
start_line: nil,
|
172
|
+
end_line: nil,
|
173
|
+
dir: +1)
|
174
|
+
#
|
175
|
+
if @target_frames && frame = @target_frames[frame_index]
|
176
|
+
if file_lines = file_lines(path = frame.location.path)
|
177
|
+
frame_line = frame.location.lineno - 1
|
178
|
+
|
179
|
+
lines = file_lines.map.with_index do |e, i|
|
180
|
+
if i == frame_line
|
181
|
+
"=> #{'%4d' % (i+1)}| #{e}"
|
182
|
+
else
|
183
|
+
" #{'%4d' % (i+1)}| #{e}"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
unless start_line
|
188
|
+
if frame.show_line
|
189
|
+
if dir > 0
|
190
|
+
start_line = frame.show_line
|
191
|
+
else
|
192
|
+
end_line = frame.show_line - max_lines
|
193
|
+
start_line = [end_line - max_lines, 0].max
|
194
|
+
end
|
195
|
+
else
|
196
|
+
start_line = [frame_line - max_lines/2, 0].max
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
unless end_line
|
201
|
+
end_line = [start_line + max_lines, lines.size].min
|
202
|
+
end
|
203
|
+
|
204
|
+
if update_line
|
205
|
+
frame.show_line = end_line
|
206
|
+
end
|
207
|
+
|
208
|
+
if start_line != end_line && max_lines
|
209
|
+
puts "[#{start_line+1}, #{end_line}] in #{pretty_path(path)}" if !update_line && max_lines != 1
|
210
|
+
puts lines[start_line ... end_line]
|
211
|
+
end
|
212
|
+
else # no file lines
|
213
|
+
puts "# No sourcefile available for #{path}"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def show_by_editor path = nil
|
219
|
+
unless path
|
220
|
+
if @target_frames && frame = @target_frames[@current_frame_index]
|
221
|
+
path = frame.location.path
|
222
|
+
else
|
223
|
+
return # can't get path
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
if File.exist?(path)
|
228
|
+
if editor = (ENV['RUBY_DEBUG_EDITOR'] || ENV['EDITOR'])
|
229
|
+
puts "command: #{editor}"
|
230
|
+
puts " path: #{path}"
|
231
|
+
system(editor, path)
|
232
|
+
else
|
233
|
+
puts "can not find editor setting: ENV['RUBY_DEBUG_EDITOR'] or ENV['EDITOR']"
|
234
|
+
end
|
235
|
+
else
|
236
|
+
puts "Can not find file: #{path}"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def show_locals
|
241
|
+
if s = current_frame&.self
|
242
|
+
puts " %self => #{s}"
|
243
|
+
end
|
244
|
+
if current_frame&.has_return_value
|
245
|
+
puts " %return => #{current_frame.return_value}"
|
246
|
+
end
|
247
|
+
if b = current_frame&.binding
|
248
|
+
b.local_variables.each{|loc|
|
249
|
+
puts " #{loc} => #{b.local_variable_get(loc).inspect}"
|
250
|
+
}
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def show_ivars
|
255
|
+
if s = current_frame&.self
|
256
|
+
s.instance_variables.each{|iv|
|
257
|
+
puts " #{iv} => #{s.instance_variable_get(iv)}"
|
258
|
+
}
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def frame_eval src, re_raise: false
|
263
|
+
begin
|
264
|
+
@success_last_eval = false
|
265
|
+
|
266
|
+
b = current_frame.binding
|
267
|
+
result = if b
|
268
|
+
f, l = b.source_location
|
269
|
+
b.eval(src, "(rdbg)/#{f}")
|
270
|
+
else
|
271
|
+
frame_self = current_frame.self
|
272
|
+
frame_self.instance_eval(src)
|
273
|
+
end
|
274
|
+
@success_last_eval = true
|
275
|
+
result
|
276
|
+
|
277
|
+
rescue Exception => e
|
278
|
+
return yield(e) if block_given?
|
279
|
+
|
280
|
+
puts "eval error: #{e}"
|
281
|
+
|
282
|
+
e.backtrace_locations.each do |loc|
|
283
|
+
break if loc.path == __FILE__
|
284
|
+
puts " #{loc}"
|
285
|
+
end
|
286
|
+
raise if re_raise
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
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
|
+
|
353
|
+
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}"
|
385
|
+
end
|
386
|
+
|
387
|
+
def show_frames max = (@target_frames || []).size
|
388
|
+
if max > 0 && frames = @target_frames
|
389
|
+
size = @target_frames.size
|
390
|
+
max += 1 if size == max + 1
|
391
|
+
max.times{|i|
|
392
|
+
break if i >= size
|
393
|
+
puts frame_str(i)
|
394
|
+
}
|
395
|
+
puts " # and #{size - max} frames (use `bt' command for all frames)" if max < size
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
def show_frame i=0
|
400
|
+
puts frame_str(i)
|
401
|
+
end
|
402
|
+
|
403
|
+
def show_object_info expr
|
404
|
+
begin
|
405
|
+
result = frame_eval(expr, re_raise: true)
|
406
|
+
rescue Exception
|
407
|
+
# ignore
|
408
|
+
else
|
409
|
+
klass = ObjectSpace.internal_class_of(result)
|
410
|
+
exists = []
|
411
|
+
klass.ancestors.each{|k|
|
412
|
+
puts "= #{k}"
|
413
|
+
if (ms = (k.instance_methods(false) - exists)).size > 0
|
414
|
+
puts ms.sort.join("\t")
|
415
|
+
exists |= ms
|
416
|
+
end
|
417
|
+
}
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def add_breakpoint args
|
422
|
+
case args.first
|
423
|
+
when :method
|
424
|
+
klass_name, op, method_name, cond = args[1..]
|
425
|
+
bp = MethodBreakpoint.new(current_frame.binding, klass_name, op, method_name, cond)
|
426
|
+
begin
|
427
|
+
bp.enable
|
428
|
+
rescue Exception => e
|
429
|
+
puts e.message
|
430
|
+
::DEBUGGER__::METHOD_ADDED_TRACKER.enable
|
431
|
+
end
|
432
|
+
event! :result, :method_breakpoint, bp
|
433
|
+
else
|
434
|
+
raise "unknown breakpoint: #{args}"
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
def set_mode mode
|
439
|
+
@mode = mode
|
440
|
+
end
|
441
|
+
|
442
|
+
def wait_next_action
|
443
|
+
set_mode :wait_next_action
|
444
|
+
|
445
|
+
while cmds = @q_cmd.pop
|
446
|
+
# pp [self, cmds: cmds]
|
447
|
+
|
448
|
+
cmd, *args = *cmds
|
449
|
+
|
450
|
+
case cmd
|
451
|
+
when :continue
|
452
|
+
break
|
453
|
+
when :step
|
454
|
+
step_type = args[0]
|
455
|
+
case step_type
|
456
|
+
when :in
|
457
|
+
step_tp{true}
|
458
|
+
when :next
|
459
|
+
frame = @target_frames.first
|
460
|
+
path = frame.location.absolute_path || "!eval:#{frame.location.path}"
|
461
|
+
line = frame.location.lineno
|
462
|
+
frame.iseq.traceable_lines_norec(lines = {})
|
463
|
+
next_line = lines.keys.bsearch{|e| e > line}
|
464
|
+
if !next_line && (last_line = frame.iseq.last_line) > line
|
465
|
+
next_line = last_line
|
466
|
+
end
|
467
|
+
depth = @target_frames.first.frame_depth
|
468
|
+
|
469
|
+
step_tp{
|
470
|
+
loc = caller_locations(2, 1).first
|
471
|
+
loc_path = loc.absolute_path || "!eval:#{loc.path}"
|
472
|
+
|
473
|
+
(next_line && loc_path == path &&
|
474
|
+
(loc_lineno = loc.lineno) > line && loc_lineno <= next_line) ||
|
475
|
+
(DEBUGGER__.frame_depth - 3 < depth)
|
476
|
+
}
|
477
|
+
when :finish
|
478
|
+
depth = @target_frames.first.frame_depth
|
479
|
+
step_tp{
|
480
|
+
# 3 is debugger's frame count
|
481
|
+
DEBUGGER__.frame_depth - 3 < depth
|
482
|
+
}
|
483
|
+
else
|
484
|
+
raise
|
485
|
+
end
|
486
|
+
break
|
487
|
+
when :eval
|
488
|
+
eval_type, eval_src = *args
|
489
|
+
|
490
|
+
case eval_type
|
491
|
+
when :display, :try_display
|
492
|
+
else
|
493
|
+
result = frame_eval(eval_src)
|
494
|
+
end
|
495
|
+
result_type = nil
|
496
|
+
|
497
|
+
case eval_type
|
498
|
+
when :p
|
499
|
+
puts "=> " + result.inspect
|
500
|
+
when :pp
|
501
|
+
puts "=> "
|
502
|
+
PP.pp(result, out = ''.dup)
|
503
|
+
puts out
|
504
|
+
when :call
|
505
|
+
result = frame_eval(eval_src)
|
506
|
+
when :display, :try_display
|
507
|
+
failed_results = []
|
508
|
+
eval_src.each_with_index{|src, i|
|
509
|
+
result = frame_eval(src){|e|
|
510
|
+
failed_results << [i, e.message]
|
511
|
+
"<error: #{e.message}>"
|
512
|
+
}
|
513
|
+
puts "#{i}: #{src} = #{result}"
|
514
|
+
}
|
515
|
+
|
516
|
+
result_type = eval_type
|
517
|
+
result = failed_results
|
518
|
+
when :watch
|
519
|
+
if @success_last_eval
|
520
|
+
puts "#{eval_src} = #{result}"
|
521
|
+
result = WatchExprBreakpoint.new(eval_src, result)
|
522
|
+
result_type = :watch
|
523
|
+
else
|
524
|
+
result = nil
|
525
|
+
end
|
526
|
+
else
|
527
|
+
raise "unknown error option: #{args.inspect}"
|
528
|
+
end
|
529
|
+
|
530
|
+
event! :result, result_type, result
|
531
|
+
when :frame
|
532
|
+
type, arg = *args
|
533
|
+
case type
|
534
|
+
when :up
|
535
|
+
if @current_frame_index + 1 < @target_frames.size
|
536
|
+
@current_frame_index += 1
|
537
|
+
show_src max_lines: 1
|
538
|
+
show_frame(@current_frame_index)
|
539
|
+
end
|
540
|
+
when :down
|
541
|
+
if @current_frame_index > 0
|
542
|
+
@current_frame_index -= 1
|
543
|
+
show_src max_lines: 1
|
544
|
+
show_frame(@current_frame_index)
|
545
|
+
end
|
546
|
+
when :set
|
547
|
+
if arg
|
548
|
+
index = arg.to_i
|
549
|
+
if index >= 0 && index < @target_frames.size
|
550
|
+
@current_frame_index = index
|
551
|
+
else
|
552
|
+
puts "out of frame index: #{index}"
|
553
|
+
end
|
554
|
+
end
|
555
|
+
show_src max_lines: 1
|
556
|
+
show_frame(@current_frame_index)
|
557
|
+
else
|
558
|
+
raise "unsupported frame operation: #{arg.inspect}"
|
559
|
+
end
|
560
|
+
event! :result, nil
|
561
|
+
when :show
|
562
|
+
type = args.shift
|
563
|
+
|
564
|
+
case type
|
565
|
+
when :backtrace
|
566
|
+
show_frames
|
567
|
+
|
568
|
+
when :list
|
569
|
+
show_src(update_line: true, **(args.first || {}))
|
570
|
+
|
571
|
+
when :edit
|
572
|
+
show_by_editor(args.first)
|
573
|
+
|
574
|
+
when :local
|
575
|
+
show_frame
|
576
|
+
show_locals
|
577
|
+
show_ivars
|
578
|
+
|
579
|
+
when :object_info
|
580
|
+
expr = args.shift
|
581
|
+
show_object_info expr
|
582
|
+
|
583
|
+
else
|
584
|
+
raise "unknown show param: " + [type, *args].inspect
|
585
|
+
end
|
586
|
+
|
587
|
+
event! :result, nil
|
588
|
+
|
589
|
+
when :breakpoint
|
590
|
+
add_breakpoint args
|
591
|
+
else
|
592
|
+
raise [cmd, *args].inspect
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
rescue SystemExit
|
597
|
+
raise
|
598
|
+
rescue Exception => e
|
599
|
+
pp [__FILE__, __LINE__, e, e.backtrace]
|
600
|
+
raise
|
601
|
+
ensure
|
602
|
+
set_mode nil
|
603
|
+
end
|
604
|
+
|
605
|
+
def to_s
|
606
|
+
loc = current_frame&.location
|
607
|
+
|
608
|
+
if loc
|
609
|
+
str = "(#{@thread.name || @thread.status})@#{loc}"
|
610
|
+
else
|
611
|
+
str = "(#{@thread.name || @thread.status})@#{@thread.to_s}"
|
612
|
+
end
|
613
|
+
|
614
|
+
str += " (not under control)" unless self.mode
|
615
|
+
str
|
616
|
+
end
|
617
|
+
end
|
618
|
+
end
|