rbx-tracer 0.0.1-universal-rubinius
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +37 -0
- data/LICENSE +25 -0
- data/NEWS +2 -0
- data/README +1 -0
- data/Rakefile +109 -0
- data/THANKS +2 -0
- data/lib/set_trace.rb +324 -0
- data/lib/set_trace.rbc +5346 -0
- data/test/test-settracefunc.rb +29 -0
- metadata +94 -0
data/ChangeLog
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
2010-12-23 rocky <rockyb@rubyforge.org>
|
2
|
+
|
3
|
+
* breakpoint.rb, frame.rb, set_trace.rb: runtime_context ->
|
4
|
+
vm_location
|
5
|
+
|
6
|
+
2010-12-22 rocky <rockyb@rubyforge.org>
|
7
|
+
|
8
|
+
* breakpoint.rb, commands.rb, frame.rb, set_trace.rb: Remove more
|
9
|
+
(but not all) of the "command"-ness for step/continue that is not
|
10
|
+
needed in set_trace_func. location -> runtime_context.
|
11
|
+
|
12
|
+
2010-12-21 rocky <rockyb@rubyforge.org>
|
13
|
+
|
14
|
+
* commands.rb, set_trace.rb: Handle set_trace_func nil.
|
15
|
+
|
16
|
+
2010-12-21 rocky <rockyb@rubyforge.org>
|
17
|
+
|
18
|
+
* breakpoint.rb, commands.rb, frame.rb, set_trace.rb: Set call and
|
19
|
+
return events
|
20
|
+
|
21
|
+
2010-12-21 rocky <rockyb@rubyforge.org>
|
22
|
+
|
23
|
+
* set_trace.rb: Closer to having all of the set_trace_func
|
24
|
+
parameters filled in.
|
25
|
+
|
26
|
+
2010-12-21 rocky <rockyb@rubyforge.org>
|
27
|
+
|
28
|
+
* README: See above
|
29
|
+
|
30
|
+
2010-12-21 rocky <rockyb@rubyforge.org>
|
31
|
+
|
32
|
+
* README: What we're about. github suggests this.
|
33
|
+
|
34
|
+
2010-12-20 rvm <rocky-rvm@static-71-183-236-17.nycmny.fios.verizon.net>
|
35
|
+
|
36
|
+
* First Hope (of sometine useful).
|
37
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (c) 2010, Rocky Bernstein
|
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
|
+
* Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
* 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
|
+
* Neither the name of the Evan Phoenix nor the names of its contributors
|
13
|
+
may be used to endorse or promote products derived from this software
|
14
|
+
without 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 THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Simulate Ruby 1.8, 1.9, JRuby's set_trace_func in Rubinius.
|
data/Rakefile
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
# Are we Rubinius? We'll test by checking the specific function we need.
|
3
|
+
raise RuntimeError, 'This package is for Rubinius 1.2 or 1.2.1dev only!' unless
|
4
|
+
Object.constants.include?('Rubinius') &&
|
5
|
+
Rubinius.constants.include?('VM') &&
|
6
|
+
%w(1.2 1.2.1dev).member?(Rubinius::VERSION)
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'rake/gempackagetask'
|
10
|
+
require 'rake/rdoctask'
|
11
|
+
require 'rake/testtask'
|
12
|
+
|
13
|
+
ROOT_DIR = File.dirname(__FILE__)
|
14
|
+
|
15
|
+
def gemspec
|
16
|
+
@gemspec ||= eval(File.read('.gemspec'), binding, '.gemspec')
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Build the gem"
|
20
|
+
task :package=>:gem
|
21
|
+
task :gem=>:gemspec do
|
22
|
+
Dir.chdir(ROOT_DIR) do
|
23
|
+
sh "gem build .gemspec"
|
24
|
+
FileUtils.mkdir_p 'pkg'
|
25
|
+
FileUtils.mv("#{gemspec.name}-#{gemspec.version}-universal-rubinius.gem",
|
26
|
+
'pkg')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Install the gem locally"
|
31
|
+
task :install => :gem do
|
32
|
+
Dir.chdir(ROOT_DIR) do
|
33
|
+
sh %{gem install --local pkg/#{gemspec.name}-#{gemspec.version}}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
require 'rbconfig'
|
38
|
+
RUBY_PATH = File.join(RbConfig::CONFIG['bindir'],
|
39
|
+
RbConfig::CONFIG['RUBY_INSTALL_NAME'])
|
40
|
+
|
41
|
+
def run_standalone_ruby_files(list)
|
42
|
+
puts '*' * 40
|
43
|
+
list.each do |ruby_file|
|
44
|
+
system(RUBY_PATH, ruby_file)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def run_standalone_ruby_file(directory)
|
49
|
+
puts ('*' * 10) + ' ' + directory + ' ' + ('*' * 10)
|
50
|
+
Dir.chdir(directory) do
|
51
|
+
Dir.glob('*.rb').each do |ruby_file|
|
52
|
+
puts(('-' * 20) + ' ' + ruby_file + ' ' + ('-' * 20))
|
53
|
+
system(RUBY_PATH, ruby_file)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
desc 'Create a GNU-style ChangeLog via git2cl'
|
59
|
+
task :ChangeLog do
|
60
|
+
system('git log --pretty --numstat --summary | git2cl > ChangeLog')
|
61
|
+
end
|
62
|
+
|
63
|
+
desc 'the tests'
|
64
|
+
Rake::TestTask.new(:'test') do |t|
|
65
|
+
t.test_files = FileList['test/test-*.rb']
|
66
|
+
# t.pattern = 'test/**/*test-*.rb' # instead of above
|
67
|
+
t.verbose = true
|
68
|
+
end
|
69
|
+
|
70
|
+
desc 'Test everything - unit tests for now.'
|
71
|
+
task :default => :test
|
72
|
+
|
73
|
+
desc "Run each Ruby app file in standalone mode."
|
74
|
+
task :'check:app' do
|
75
|
+
run_standalone_ruby_file(File.join(%W(#{ROOT_DIR} app)))
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "Generate the gemspec"
|
79
|
+
task :generate do
|
80
|
+
puts gemspec.to_ruby
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "Validate the gemspec"
|
84
|
+
task :gemspec do
|
85
|
+
gemspec.validate
|
86
|
+
end
|
87
|
+
|
88
|
+
# --------- RDoc Documentation ------
|
89
|
+
desc "Generate rdoc documentation"
|
90
|
+
Rake::RDocTask.new("rdoc") do |rdoc|
|
91
|
+
rdoc.rdoc_dir = 'doc'
|
92
|
+
# rdoc.title = "rbx-trepaning #{Rubinius::SetTrace::VERSION} Documentation"
|
93
|
+
|
94
|
+
rdoc.rdoc_files.include(%w(lib/set_trace.rb app/*.rb))
|
95
|
+
end
|
96
|
+
|
97
|
+
desc "Same as rdoc"
|
98
|
+
task :doc => :rdoc
|
99
|
+
|
100
|
+
task :clobber_package do
|
101
|
+
FileUtils.rm_rf File.join(ROOT_DIR, 'pkg')
|
102
|
+
end
|
103
|
+
|
104
|
+
task :clobber_rdoc do
|
105
|
+
FileUtils.rm_rf File.join(ROOT_DIR, 'doc')
|
106
|
+
end
|
107
|
+
|
108
|
+
desc "Remove built files"
|
109
|
+
task :clean => [:clobber_package, :clobber_rdoc]
|
data/THANKS
ADDED
data/lib/set_trace.rb
ADDED
@@ -0,0 +1,324 @@
|
|
1
|
+
require 'rubygems'; require 'require_relative'
|
2
|
+
require_relative '../app/frame'
|
3
|
+
require_relative '../app/stepping'
|
4
|
+
require_relative '../app/breakpoint'
|
5
|
+
require 'compiler/iseq'
|
6
|
+
|
7
|
+
#
|
8
|
+
# A Rubinius implementation of set_trace_func.
|
9
|
+
#
|
10
|
+
# This code is wired into the debugging APIs provided by Rubinius.
|
11
|
+
# It tries to isolate the complicated stepping logic as well as simulate
|
12
|
+
# Ruby's set_trace_func.
|
13
|
+
#
|
14
|
+
|
15
|
+
class Rubinius::SetTrace
|
16
|
+
VERSION = '0.0.1'
|
17
|
+
|
18
|
+
DEFAULT_SET_TRACE_FUNC_OPTS = {
|
19
|
+
:callback_style => :classic,
|
20
|
+
:step_to_parent => :true
|
21
|
+
}
|
22
|
+
|
23
|
+
# Create a new object. Stepping starts up a thread which is where
|
24
|
+
# the callback executes from. Other threads are told that their
|
25
|
+
# debugging thread is the stepping thread, and this control of execution
|
26
|
+
# is handled.
|
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
|
+
@user_variables = 0
|
45
|
+
@breakpoints = []
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :variables, :current_frame, :breakpoints, :user_variables
|
49
|
+
attr_reader :vm_locations
|
50
|
+
|
51
|
+
def self.global(opts={})
|
52
|
+
@global ||= new
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.set_trace_func(callback_method, opts={})
|
56
|
+
opts = Rubinius::SetTrace::DEFAULT_SET_TRACE_FUNC_OPTS.merge(opts)
|
57
|
+
opts[:call_offset] ||= 1
|
58
|
+
global.set_trace_func(callback_method, opts)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Startup the stepping, skipping back +opts[:call_offset]+
|
62
|
+
# frames. This lets you start stepping straight into caller's
|
63
|
+
# method.
|
64
|
+
#
|
65
|
+
def set_trace_func(callback_method, opts={})
|
66
|
+
if callback_method
|
67
|
+
@opts = opts
|
68
|
+
call_offset = @opts[:call_offset] || 0
|
69
|
+
@callback_method = callback_method
|
70
|
+
@tracing = true
|
71
|
+
spinup_thread
|
72
|
+
|
73
|
+
# Feed info to the stepping call-back thread!
|
74
|
+
@vm_locations = Rubinius::VM.backtrace(call_offset + 1, true)
|
75
|
+
|
76
|
+
method = Rubinius::CompiledMethod.of_sender
|
77
|
+
|
78
|
+
bp = BreakPoint.new "<start>", method, 0, 0
|
79
|
+
channel = Rubinius::Channel.new
|
80
|
+
|
81
|
+
@local_channel.send Rubinius::Tuple[bp, Thread.current, channel,
|
82
|
+
@vm_locations]
|
83
|
+
|
84
|
+
# wait for the callback to release us
|
85
|
+
channel.receive
|
86
|
+
|
87
|
+
Thread.current.set_debugger_thread @thread
|
88
|
+
self
|
89
|
+
else
|
90
|
+
@tracing = false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Stop and wait for a debuggee thread to send us info about
|
95
|
+
# stopping at a breakpoint.
|
96
|
+
#
|
97
|
+
def listen(step_into=false)
|
98
|
+
while true
|
99
|
+
if @channel
|
100
|
+
if step_into
|
101
|
+
@channel << :step
|
102
|
+
else
|
103
|
+
@channel << true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Wait for someone to stop
|
108
|
+
@breakpoint, @debugee_thread, @channel, @vm_locations =
|
109
|
+
@local_channel.receive
|
110
|
+
|
111
|
+
# Uncache all frames since we stopped at a new place
|
112
|
+
@frames = []
|
113
|
+
|
114
|
+
@current_frame = frame(0)
|
115
|
+
|
116
|
+
if @breakpoint
|
117
|
+
@event = @breakpoint.event
|
118
|
+
# Only break out if the hit was valid
|
119
|
+
break if @breakpoint.hit!(@vm_locations.first)
|
120
|
+
else
|
121
|
+
@event = 'call'
|
122
|
+
break
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# puts
|
127
|
+
# puts "Breakpoint: #{@current_frame.describe}"
|
128
|
+
# show_code
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
# call callback
|
133
|
+
def call_callback
|
134
|
+
case @opts[:callback_style]
|
135
|
+
when :classic
|
136
|
+
line = @current_frame.line
|
137
|
+
file = @current_frame.file
|
138
|
+
meth = @current_frame.method
|
139
|
+
binding = @current_frame.binding
|
140
|
+
id = nil
|
141
|
+
classname = nil
|
142
|
+
@callback_method.call(@event, file, line, id, binding, classname)
|
143
|
+
else
|
144
|
+
@callback_method.call(@event, @vm_locations)
|
145
|
+
end
|
146
|
+
if @tracing
|
147
|
+
if @opts[:step_to_parent]
|
148
|
+
@opts[:step_to_parent] = false
|
149
|
+
step_to_parent
|
150
|
+
else
|
151
|
+
step_over_by(1)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
listen
|
155
|
+
end
|
156
|
+
|
157
|
+
def frame(num)
|
158
|
+
@frames[num] ||= Frame.new(self, num, @vm_locations[num])
|
159
|
+
end
|
160
|
+
|
161
|
+
def delete_breakpoint(i)
|
162
|
+
bp = @breakpoints[i-1]
|
163
|
+
|
164
|
+
unless bp
|
165
|
+
STDERR.puts "Unknown breakpoint '#{i}'"
|
166
|
+
return
|
167
|
+
end
|
168
|
+
|
169
|
+
bp.delete!
|
170
|
+
|
171
|
+
@breakpoints[i-1] = nil
|
172
|
+
end
|
173
|
+
|
174
|
+
def send_between(exec, start, fin)
|
175
|
+
ss = Rubinius::InstructionSet.opcodes_map[:send_stack]
|
176
|
+
sm = Rubinius::InstructionSet.opcodes_map[:send_method]
|
177
|
+
sb = Rubinius::InstructionSet.opcodes_map[:send_stack_with_block]
|
178
|
+
|
179
|
+
iseq = exec.iseq
|
180
|
+
|
181
|
+
fin = iseq.size if fin < 0
|
182
|
+
|
183
|
+
i = start
|
184
|
+
while i < fin
|
185
|
+
op = iseq[i]
|
186
|
+
case op
|
187
|
+
when ss, sm, sb
|
188
|
+
return exec.literals[iseq[i + 1]]
|
189
|
+
else
|
190
|
+
op = Rubinius::InstructionSet[op]
|
191
|
+
i += (op.arg_count + 1)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
return nil
|
196
|
+
end
|
197
|
+
|
198
|
+
def show_code(line=@current_frame.line)
|
199
|
+
path = @current_frame.method.active_path
|
200
|
+
|
201
|
+
if str = @file_lines[path][line - 1]
|
202
|
+
puts "#{line}: #{str}"
|
203
|
+
else
|
204
|
+
show_bytecode(line)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def decode_one
|
209
|
+
ip = @current_frame.ip
|
210
|
+
|
211
|
+
meth = @current_frame.method
|
212
|
+
decoder = Rubinius::InstructionDecoder.new(meth.iseq)
|
213
|
+
partial = decoder.decode_between(ip, ip+1)
|
214
|
+
|
215
|
+
partial.each do |ins|
|
216
|
+
op = ins.shift
|
217
|
+
|
218
|
+
ins.each_index do |i|
|
219
|
+
case op.args[i]
|
220
|
+
when :literal
|
221
|
+
ins[i] = meth.literals[ins[i]].inspect
|
222
|
+
when :local
|
223
|
+
if meth.local_names
|
224
|
+
ins[i] = meth.local_names[ins[i]]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
display "ip #{ip} = #{op.opcode} #{ins.join(', ')}"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def show_bytecode(line=@current_frame.line)
|
234
|
+
meth = @current_frame.method
|
235
|
+
start = meth.first_ip_on_line(line)
|
236
|
+
fin = meth.first_ip_on_line(line+1)
|
237
|
+
|
238
|
+
if fin == -1
|
239
|
+
fin = meth.iseq.size
|
240
|
+
end
|
241
|
+
|
242
|
+
puts "Bytecode between #{start} and #{fin-1} for line #{line}"
|
243
|
+
|
244
|
+
decoder = Rubinius::InstructionDecoder.new(meth.iseq)
|
245
|
+
partial = decoder.decode_between(start, fin)
|
246
|
+
|
247
|
+
ip = start
|
248
|
+
|
249
|
+
partial.each do |ins|
|
250
|
+
op = ins.shift
|
251
|
+
|
252
|
+
ins.each_index do |i|
|
253
|
+
case op.args[i]
|
254
|
+
when :literal
|
255
|
+
ins[i] = meth.literals[ins[i]].inspect
|
256
|
+
when :local
|
257
|
+
if meth.local_names
|
258
|
+
ins[i] = meth.local_names[ins[i]]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
puts " %4d: #{op.opcode} #{ins.join(', ')}" % ip
|
264
|
+
|
265
|
+
ip += (ins.size + 1)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def spinup_thread
|
270
|
+
return if @thread
|
271
|
+
|
272
|
+
@local_channel = Rubinius::Channel.new
|
273
|
+
|
274
|
+
@thread = Thread.new do
|
275
|
+
begin
|
276
|
+
listen
|
277
|
+
rescue Exception => e
|
278
|
+
e.render("Listening")
|
279
|
+
break
|
280
|
+
end
|
281
|
+
|
282
|
+
while true
|
283
|
+
begin
|
284
|
+
call_callback
|
285
|
+
rescue Exception => e
|
286
|
+
begin
|
287
|
+
e.render "Error in debugger"
|
288
|
+
rescue Exception => e2
|
289
|
+
STDERR.puts "Error rendering backtrace in debugger!"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
@thread.setup_control!(@local_channel)
|
296
|
+
end
|
297
|
+
|
298
|
+
private :spinup_thread
|
299
|
+
|
300
|
+
end
|
301
|
+
|
302
|
+
module Kernel
|
303
|
+
def set_trace_func(callback_method, opts={})
|
304
|
+
opts = Rubinius::SetTrace::DEFAULT_SET_TRACE_FUNC_OPTS.merge(opts)
|
305
|
+
Rubinius::SetTrace.set_trace_func(callback_method, opts)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
if __FILE__ == $0
|
310
|
+
if ARGV[0] == 'classic'
|
311
|
+
meth = lambda { |event, file, line, id, binding, classname|
|
312
|
+
puts "tracer: #{event} #{file}:#{line}"
|
313
|
+
}
|
314
|
+
set_trace_func meth
|
315
|
+
else
|
316
|
+
meth = lambda { |event, vm_locs|
|
317
|
+
puts "tracer: #{event} #{vm_locs[0].file}:#{vm_locs[0].line}"
|
318
|
+
}
|
319
|
+
set_trace_func(meth, {:callback_style => :new})
|
320
|
+
end
|
321
|
+
x = 1
|
322
|
+
y = 2
|
323
|
+
set_trace_func nil
|
324
|
+
end
|