rubinius-debugger 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6f30dc2838cb2fb12f0164685407943e685081f6
4
+ data.tar.gz: 253e3ffb5d6daf87b0aeb354dee56f9216b97829
5
+ SHA512:
6
+ metadata.gz: e7940eb52a17f41399613694c1e3d002ec104b7f8cbc50d37301633ca3dcdb2d3d90139de24eb36ded0247b09df65c76a93cf9b790566fa5ac8639452ebd2bc5
7
+ data.tar.gz: de1fb048ae548256c7d4db617f3db787d114b0236856aee5ec06b8b708ef0df3476792314806bf543306af756684d34413c6e69c94e9599b92822a50e606bc62
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rubinius-debugger.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2013, Brian Shirai
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ 3. Neither the name of the library nor the names of its contributors may be
13
+ used to endorse or promote products derived from this software without
14
+ specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT,
20
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
25
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,29 @@
1
+ # Rubinius::Debugger
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'rubinius-debugger'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install rubinius-debugger
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,486 @@
1
+ require 'rubysl/readline'
2
+ require 'rubinius/debugger/frame'
3
+ require 'rubinius/debugger/commands'
4
+ require 'rubinius/debugger/breakpoint'
5
+ require 'rubinius/debugger/display'
6
+ require 'rubinius/compiler/iseq'
7
+
8
+ #
9
+ # The Rubinius reference debugger.
10
+ #
11
+ # This debugger is wired into the debugging APIs provided by Rubinius.
12
+ # It serves as a simple, builtin debugger that others can use as
13
+ # an example for how to build a better debugger.
14
+ #
15
+
16
+ class Rubinius::Debugger
17
+ include Rubinius::Debugger::Display
18
+
19
+ # Find the source for the kernel.
20
+ ROOT_DIR = File.expand_path("../", Rubinius::KERNEL_PATH)
21
+
22
+ # Create a new debugger object. The debugger starts up a thread
23
+ # which is where the command line interface executes from. Other
24
+ # threads that you wish to debug are told that their debugging
25
+ # thread is the debugger thread. This is how the debugger is handed
26
+ # control of execution.
27
+ #
28
+ def initialize
29
+ @file_lines = Hash.new do |hash, path|
30
+ if File.exists? path
31
+ hash[path] = File.readlines(path)
32
+ else
33
+ ab_path = File.join(@root_dir, path)
34
+ if File.exists? ab_path
35
+ hash[path] = File.readlines(ab_path)
36
+ else
37
+ hash[path] = []
38
+ end
39
+ end
40
+ end
41
+
42
+ @thread = nil
43
+ @frames = []
44
+
45
+ @variables = {
46
+ :show_ip => false,
47
+ :show_bytecode => false,
48
+ :highlight => false,
49
+ :list_command_history => {
50
+ :path => nil,
51
+ :center_line => nil
52
+ }
53
+ }
54
+
55
+ @loaded_hook = proc { |file|
56
+ check_deferred_breakpoints
57
+ }
58
+
59
+ @added_hook = proc { |mod, name, exec|
60
+ check_deferred_breakpoints
61
+ }
62
+
63
+ # Use a few Rubinius specific hooks to trigger checking
64
+ # for deferred breakpoints.
65
+
66
+ Rubinius::CodeLoader.loaded_hook.add @loaded_hook
67
+ Rubinius.add_method_hook.add @added_hook
68
+
69
+ @deferred_breakpoints = []
70
+
71
+ @user_variables = 0
72
+
73
+ @breakpoints = []
74
+
75
+ @history_path = File.expand_path("~/.rbx_debug")
76
+
77
+ if File.exists?(@history_path)
78
+ File.readlines(@history_path).each do |line|
79
+ Readline::HISTORY << line.strip
80
+ end
81
+ @history_io = File.new(@history_path, "a")
82
+ else
83
+ @history_io = File.new(@history_path, "w")
84
+ end
85
+
86
+ @history_io.sync = true
87
+
88
+ @root_dir = ROOT_DIR
89
+ end
90
+
91
+ attr_reader :variables, :current_frame, :breakpoints, :user_variables
92
+ attr_reader :locations
93
+
94
+ def self.global
95
+ @global ||= new
96
+ end
97
+
98
+ def self.start
99
+ global.start(1)
100
+ end
101
+
102
+ # This is simplest API point. This starts up the debugger in the caller
103
+ # of this method to begin debugging.
104
+ #
105
+ def self.here
106
+ global.start(1)
107
+ end
108
+
109
+ # Startup the debugger, skipping back +offset+ frames. This lets you start
110
+ # the debugger straight into callers method.
111
+ #
112
+ def start(offset=0)
113
+ spinup_thread
114
+
115
+ # Feed info to the debugger thread!
116
+ locs = Rubinius::VM.backtrace(offset + 1, true)
117
+
118
+ method = Rubinius::CompiledCode.of_sender
119
+
120
+ bp = BreakPoint.new "<start>", method, 0, 0
121
+ channel = Rubinius::Channel.new
122
+
123
+ @local_channel.send Rubinius::Tuple[bp, Thread.current, channel, locs]
124
+
125
+ # wait for the debugger to release us
126
+ channel.receive
127
+
128
+ Thread.current.set_debugger_thread @thread
129
+ self
130
+ end
131
+
132
+ # Stop and wait for a debuggee thread to send us info about
133
+ # stoping at a breakpoint.
134
+ #
135
+ def listen(step_into=false)
136
+ while true
137
+ if @channel
138
+ if step_into
139
+ @channel << :step
140
+ else
141
+ @channel << true
142
+ end
143
+ end
144
+
145
+ # Wait for someone to stop
146
+ bp, thr, chan, locs = @local_channel.receive
147
+
148
+ # Uncache all frames since we stopped at a new place
149
+ @frames = []
150
+
151
+ @locations = locs
152
+ @breakpoint = bp
153
+ @debuggee_thread = thr
154
+ @channel = chan
155
+
156
+ @current_frame = frame(0)
157
+
158
+ if bp
159
+ # Only break out if the hit was valid
160
+ if bp.hit!(locs.first)
161
+ if bp.has_condition?
162
+ break if @current_frame.run(bp.condition)
163
+ else
164
+ break
165
+ end
166
+ end
167
+ else
168
+ break
169
+ end
170
+ end
171
+
172
+ puts
173
+ info "Breakpoint: #{@current_frame.describe}"
174
+ show_code
175
+ eval_code(@breakpoint.commands) if @breakpoint && @breakpoint.has_commands?
176
+
177
+ if @variables[:show_bytecode]
178
+ decode_one
179
+ end
180
+
181
+ end
182
+
183
+ # Get a command from the user to run using readline
184
+ #
185
+ def accept_commands
186
+ command_list_code = []
187
+ cmd = Readline.readline "debug> "
188
+
189
+ if cmd.nil?
190
+ # ^D was entered
191
+ cmd = "quit"
192
+ elsif cmd.empty?
193
+ cmd = @last_command
194
+ else
195
+ @last_command = cmd
196
+ end
197
+
198
+ command, args = cmd.to_s.strip.split(/\s+/, 2)
199
+
200
+ runner = Command.commands.find { |k| k.match?(command) }
201
+
202
+ if runner
203
+ if runner == Command::CommandsList
204
+ bp_id = (args || @breakpoints.size).to_i
205
+
206
+ if @breakpoints.empty?
207
+ puts "No breakpoint set"
208
+ return
209
+ elsif bp_id > @breakpoints.size || bp_id < 1
210
+ puts "Invalid breakpoint number."
211
+ return
212
+ end
213
+
214
+ puts "Type commands for breakpoint ##{bp_id}, one per line."
215
+ puts "End with a line saying just 'END'."
216
+ code = Readline.readline "> "
217
+ while code != 'END'
218
+ command_list_code << code
219
+ code = Readline.readline "> "
220
+ end
221
+ args = {
222
+ :bp_id => bp_id,
223
+ :code => command_list_code.empty? ? nil : command_list_code.join(";")
224
+ }
225
+ end
226
+
227
+ runner.new(self).run args
228
+ else
229
+ puts "Unrecognized command: #{command}"
230
+ return
231
+ end
232
+
233
+ # Save it to the history.
234
+ @history_io.puts cmd
235
+ unless command_list_code.empty?
236
+ command_list_code << "END"
237
+ @history_io.puts command_list_code.join("\n")
238
+ end
239
+ end
240
+
241
+ def eval_code(args)
242
+ obj = @current_frame.run(args)
243
+
244
+ idx = @user_variables
245
+ @user_variables += 1
246
+
247
+ str = "$d#{idx}"
248
+ Rubinius::Globals[str.to_sym] = obj
249
+ puts "#{str} = #{obj.inspect}\n"
250
+ end
251
+
252
+ def frame(num)
253
+ @frames[num] ||= Frame.new(self, num, @locations[num])
254
+ end
255
+
256
+ def set_frame(num)
257
+ @current_frame = frame(num)
258
+ end
259
+
260
+ def each_frame(start=0)
261
+ start = start.number if start.kind_of?(Frame)
262
+
263
+ start.upto(@locations.size-1) do |idx|
264
+ yield frame(idx)
265
+ end
266
+ end
267
+
268
+ def set_breakpoint_method(descriptor, method, line=nil, condition=nil)
269
+ exec = method.executable
270
+
271
+ unless exec.kind_of?(Rubinius::CompiledCode)
272
+ error "Unsupported method type: #{exec.class}"
273
+ return
274
+ end
275
+
276
+ if line
277
+ ip = exec.first_ip_on_line(line)
278
+
279
+ if !ip
280
+ error "Unknown line '#{line}' in method '#{method.name}'"
281
+ return
282
+ end
283
+ else
284
+ line = exec.first_line
285
+ ip = 0
286
+ end
287
+
288
+ bp = BreakPoint.new(descriptor, exec, ip, line, condition)
289
+ bp.activate
290
+
291
+ @breakpoints << bp
292
+
293
+ info "Set breakpoint #{@breakpoints.size}: #{bp.location}"
294
+
295
+ return bp
296
+ end
297
+
298
+ def delete_breakpoint(i)
299
+ bp = @breakpoints[i-1]
300
+
301
+ unless bp
302
+ error "Unknown breakpoint '#{i}'"
303
+ return
304
+ end
305
+
306
+ bp.delete!
307
+
308
+ @breakpoints[i-1] = nil
309
+ end
310
+
311
+ def add_deferred_breakpoint(klass_name, which, name, line)
312
+ dbp = DeferredBreakPoint.new(self, @current_frame, klass_name, which, name,
313
+ line, @deferred_breakpoints)
314
+ @deferred_breakpoints << dbp
315
+ @breakpoints << dbp
316
+ end
317
+
318
+ def check_deferred_breakpoints
319
+ @deferred_breakpoints.delete_if do |bp|
320
+ bp.resolve!
321
+ end
322
+ end
323
+
324
+ def send_between(exec, start, fin)
325
+ ss = Rubinius::InstructionSet.opcodes_map[:send_stack]
326
+ sm = Rubinius::InstructionSet.opcodes_map[:send_method]
327
+ sb = Rubinius::InstructionSet.opcodes_map[:send_stack_with_block]
328
+
329
+ iseq = exec.iseq
330
+
331
+ fin = iseq.size if fin < 0
332
+
333
+ i = start
334
+ while i < fin
335
+ op = iseq[i]
336
+ case op
337
+ when ss, sm, sb
338
+ return exec.literals[iseq[i + 1]]
339
+ else
340
+ op = Rubinius::InstructionSet[op]
341
+ i += (op.arg_count + 1)
342
+ end
343
+ end
344
+
345
+ return nil
346
+ end
347
+
348
+ def list_code_around_line(path, center_line, lines_to_show)
349
+ lines_around = lines_to_show / 2
350
+ start_line = center_line - lines_around
351
+ end_line = center_line + lines_around
352
+
353
+ list_code_range(path, start_line, end_line, center_line)
354
+ end
355
+
356
+ def list_code_range(path, start_line, end_line, center_line)
357
+ if !File.exists?(path) && !File.exists?(File.join(@root_dir, path))
358
+ error "Cannot find file #{path}"
359
+ return
360
+ end
361
+
362
+ if start_line > @file_lines[path].size
363
+ error "Line number #{@file_lines[path].size + 1} out of range: #{path} has #{@file_lines[path].size} lines."
364
+ return
365
+ end
366
+
367
+ start_line = 1 if start_line < 1
368
+ end_line = @file_lines[path].size if end_line > @file_lines[path].size
369
+
370
+ @variables[:list_command_history][:path] = path
371
+ @variables[:list_command_history][:center_line] = center_line
372
+
373
+ (start_line).upto(end_line) do |i|
374
+ show_code(i, path)
375
+ end
376
+ end
377
+
378
+ def show_code(line = @current_frame.line,
379
+ path = @current_frame.method.active_path)
380
+ if str = @file_lines[path][line - 1]
381
+ if @variables[:highlight]
382
+ if fin = @current_frame.method.first_ip_on_line(line + 1)
383
+ if name = send_between(@current_frame.method, @current_frame.ip, fin)
384
+ str = str.gsub name.to_s, "\033[0;4m#{name}\033[0m"
385
+ end
386
+ end
387
+ end
388
+ info "#{line}: #{str}"
389
+ else
390
+ show_bytecode(line)
391
+ end
392
+ end
393
+
394
+ def decode_one
395
+ ip = @current_frame.ip
396
+
397
+ meth = @current_frame.method
398
+ decoder = Rubinius::InstructionDecoder.new(meth.iseq)
399
+ partial = decoder.decode_between(ip, ip+1)
400
+
401
+ partial.each do |ins|
402
+ op = ins.shift
403
+
404
+ ins.each_index do |i|
405
+ case op.args[i]
406
+ when :literal
407
+ ins[i] = meth.literals[ins[i]].inspect
408
+ when :local
409
+ if meth.local_names
410
+ ins[i] = meth.local_names[ins[i]]
411
+ end
412
+ end
413
+ end
414
+
415
+ display "ip #{ip} = #{op.opcode} #{ins.join(', ')}"
416
+ end
417
+ end
418
+
419
+ def show_bytecode(line=@current_frame.line)
420
+ meth = @current_frame.method
421
+ start = meth.first_ip_on_line(line)
422
+ fin = meth.first_ip_on_line(line+1)
423
+
424
+ if !fin
425
+ fin = meth.iseq.size
426
+ end
427
+
428
+ section "Bytecode between #{start} and #{fin-1} for line #{line}"
429
+
430
+ decoder = Rubinius::InstructionDecoder.new(meth.iseq)
431
+ partial = decoder.decode_between(start, fin)
432
+
433
+ ip = start
434
+
435
+ partial.each do |ins|
436
+ op = ins.shift
437
+
438
+ ins.each_index do |i|
439
+ case op.args[i]
440
+ when :literal
441
+ ins[i] = meth.literals[ins[i]].inspect
442
+ when :local
443
+ if meth.local_names
444
+ ins[i] = meth.local_names[ins[i]]
445
+ end
446
+ end
447
+ end
448
+
449
+ info " %4d: #{op.opcode} #{ins.join(', ')}" % ip
450
+
451
+ ip += (ins.size + 1)
452
+ end
453
+ end
454
+
455
+ def spinup_thread
456
+ return if @thread
457
+
458
+ @local_channel = Rubinius::Channel.new
459
+
460
+ @thread = Thread.new do
461
+ begin
462
+ listen
463
+ rescue Exception => e
464
+ e.render("Listening")
465
+ break
466
+ end
467
+
468
+ while true
469
+ begin
470
+ accept_commands
471
+ rescue Exception => e
472
+ begin
473
+ e.render "Error in debugger"
474
+ rescue Exception => e2
475
+ puts "Error rendering backtrace in debugger!"
476
+ end
477
+ end
478
+ end
479
+ end
480
+
481
+ @thread.setup_control!(@local_channel)
482
+ end
483
+
484
+ private :spinup_thread
485
+
486
+ end