rubinius-debugger 2.0.0

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,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