rtinspect 0.0.1 → 0.0.19
Sign up to get free protection for your applications and to get access to all the features.
- data/README +84 -50
- data/TODO +18 -7
- data/VERSION +1 -0
- data/bin/rti +69 -0
- data/lib/rti/manager.rb +411 -0
- data/lib/rti/redirect.rb +102 -0
- data/lib/rti/state.rb +65 -0
- data/lib/rti/thread.rb +553 -0
- data/lib/rti/tracer.rb +71 -0
- data/lib/rtinspect.rb +76 -899
- data/test/test.rb +248 -17
- metadata +13 -6
data/README
CHANGED
@@ -50,16 +50,16 @@ thread's inspection abilities.
|
|
50
50
|
Connected to localhost.
|
51
51
|
Escape character is '^]'.
|
52
52
|
|
53
|
-
|
53
|
+
myapp:001:0> local_variables
|
54
54
|
=> ["rti"]
|
55
55
|
|
56
|
-
|
56
|
+
myapp:002:0> rti.state
|
57
57
|
=> {:socket=>#<TCPSocket:0xb7cb92d0 127.0.0.1:48720>, :rti=>#<RuntimeInspectionThread::RTIManager:0xb7cb5f18 @tracing_proc=#<Proc:0xb7cbe974@rtinspect.rb:511>, @breakpoints={}, @state={...}>, :cmd_count=>2, :safe_level=>3, :block_count=>0, :block_cmd=>"", :use_yaml=>false, :eval_timeout=>60}
|
58
58
|
|
59
|
-
|
59
|
+
myapp:003:0> rti.state.use_yaml = true
|
60
60
|
=> --- true
|
61
61
|
|
62
|
-
|
62
|
+
myapp:004:0> rti.state
|
63
63
|
=> --- &id001 !map:RuntimeInspectionThread::State
|
64
64
|
:socket: !ruby/object:TCPSocket
|
65
65
|
peerstr: 127.0.0.1:48720
|
@@ -76,24 +76,24 @@ thread's inspection abilities.
|
|
76
76
|
:use_yaml: true
|
77
77
|
:eval_timeout: 60
|
78
78
|
|
79
|
-
|
79
|
+
myapp:005:0> rti.state.block_count = 1
|
80
80
|
=> --- 1
|
81
81
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
82
|
+
myapp:006:1> 2.times do
|
83
|
+
myapp:007:1> |i|
|
84
|
+
myapp:008:1> p i
|
85
|
+
myapp:009:1> end
|
86
|
+
myapp:010:1>
|
87
87
|
0
|
88
88
|
1
|
89
89
|
=> --- 2
|
90
90
|
|
91
|
-
|
92
|
-
|
91
|
+
myapp:011:2> exit
|
92
|
+
myapp:012:2>
|
93
93
|
=> --- !ruby/exception:SystemExit
|
94
94
|
message: "(eval):1:in `exit': exit"
|
95
95
|
|
96
|
-
|
96
|
+
myapp:013:3> ^]
|
97
97
|
|
98
98
|
telnet> quit
|
99
99
|
Connection closed.
|
@@ -114,7 +114,7 @@ value increases each time a full block is evaluated.
|
|
114
114
|
Here's what happened on the server's side (note, it's running with
|
115
115
|
debug enabled so we can see the details):
|
116
116
|
|
117
|
-
$ ruby -d
|
117
|
+
$ ruby -d -Ilib test/myapp.rb
|
118
118
|
Runtime inspection available at localhost:56789
|
119
119
|
Connection established with 127.0.0.1:48720
|
120
120
|
Executing [3]: local_variables
|
@@ -142,57 +142,57 @@ what might be expected.
|
|
142
142
|
Connected to localhost.
|
143
143
|
Escape character is '^]'.
|
144
144
|
|
145
|
-
|
145
|
+
myapp:001:0> .bp_add Foo#bar
|
146
146
|
=> "Added breakpoint 1"
|
147
147
|
|
148
|
-
|
148
|
+
myapp:002:0> start_foo
|
149
149
|
=> #<Thread:0xb7d22974 sleep>
|
150
150
|
|
151
|
-
|
151
|
+
myapp:003:0> .bp_start
|
152
152
|
=> nil
|
153
153
|
|
154
|
-
Breakpoint 1 in Foo#bar from
|
155
|
-
|
154
|
+
Breakpoint 1 in Foo#bar from myapp.rb:10 (call)
|
155
|
+
myapp:004:0> local_variables
|
156
156
|
=> ["b", "rti"]
|
157
157
|
|
158
|
-
Breakpoint 1 in Foo#bar from
|
159
|
-
|
158
|
+
Breakpoint 1 in Foo#bar from myapp.rb:10 (call)
|
159
|
+
myapp:005:0> b
|
160
160
|
=> nil
|
161
161
|
|
162
|
-
Breakpoint 1 in Foo#bar from
|
163
|
-
|
162
|
+
Breakpoint 1 in Foo#bar from myapp.rb:10 (call)
|
163
|
+
myapp:006:0> b = 1003
|
164
164
|
=> 1003
|
165
165
|
|
166
|
-
Breakpoint 1 in Foo#bar from
|
167
|
-
|
166
|
+
Breakpoint 1 in Foo#bar from myapp.rb:10 (call)
|
167
|
+
myapp:007:0> @a
|
168
168
|
=> 3
|
169
169
|
|
170
|
-
Breakpoint 1 in Foo#bar from
|
171
|
-
|
170
|
+
Breakpoint 1 in Foo#bar from myapp.rb:10 (call)
|
171
|
+
myapp:008:0> @a = 11
|
172
172
|
=> 11
|
173
173
|
|
174
|
-
Breakpoint 1 in Foo#bar from
|
175
|
-
|
174
|
+
Breakpoint 1 in Foo#bar from myapp.rb:10 (call)
|
175
|
+
myapp:009:0> .bp_next
|
176
176
|
=> nil
|
177
177
|
|
178
|
-
Breakpoint 1 in Foo#bar from
|
179
|
-
|
178
|
+
Breakpoint 1 in Foo#bar from myapp.rb:11 (line)
|
179
|
+
myapp:010:0> .bp_continue
|
180
180
|
=> nil
|
181
181
|
|
182
|
-
Breakpoint 1 in Foo#bar from
|
183
|
-
|
182
|
+
Breakpoint 1 in Foo#bar from myapp.rb:10 (call)
|
183
|
+
myapp:011:0> b
|
184
184
|
=> nil
|
185
185
|
|
186
|
-
Breakpoint 1 in Foo#bar from
|
187
|
-
|
186
|
+
Breakpoint 1 in Foo#bar from myapp.rb:10 (call)
|
187
|
+
myapp:012:0> p @a
|
188
188
|
12
|
189
189
|
=> nil
|
190
190
|
|
191
|
-
Breakpoint 1 in Foo#bar from
|
192
|
-
|
191
|
+
Breakpoint 1 in Foo#bar from myapp.rb:10 (call)
|
192
|
+
myapp:013:0> .bp_stop
|
193
193
|
=> nil
|
194
194
|
|
195
|
-
|
195
|
+
myapp:014:0> ^]
|
196
196
|
|
197
197
|
telnet> quit
|
198
198
|
Connection closed.
|
@@ -210,7 +210,7 @@ begins printing out the value of its member variable, @a. Notice that
|
|
210
210
|
the value jumps from 2 to 11 after the breakpoint is hit and the
|
211
211
|
client has adjusted the value.
|
212
212
|
|
213
|
-
$ ruby -d
|
213
|
+
$ ruby -d -Ilib test/myapp.rb
|
214
214
|
Runtime inspection available at localhost:56789
|
215
215
|
Connection established with 127.0.0.1:40202
|
216
216
|
Executing [3]: start_foo
|
@@ -243,33 +243,33 @@ it using methods added to the object dynamically.
|
|
243
243
|
Connected to localhost.
|
244
244
|
Escape character is '^]'.
|
245
245
|
|
246
|
-
|
246
|
+
myapp:001:0> start_foo
|
247
247
|
=> #<Thread:0xb7d11d04 sleep>
|
248
248
|
|
249
|
-
|
249
|
+
myapp:002:0> f = rti.get_object('Foo')
|
250
250
|
=> #<Foo:0xb7d116b0 @a=2>
|
251
251
|
|
252
|
-
|
252
|
+
myapp:003:0> rti.state.block_count = 1
|
253
253
|
=> 1
|
254
254
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
255
|
+
myapp:004:1> def f.seeit
|
256
|
+
myapp:005:1> @a
|
257
|
+
myapp:006:1> end
|
258
|
+
myapp:007:1>
|
259
259
|
=> nil
|
260
260
|
|
261
|
-
|
262
|
-
|
261
|
+
myapp:008:2> f.seeit
|
262
|
+
myapp:009:2>
|
263
263
|
=> 11
|
264
264
|
|
265
|
-
|
265
|
+
myapp:010:3> ^]
|
266
266
|
|
267
267
|
telnet> quit
|
268
268
|
Connection closed.
|
269
269
|
|
270
270
|
No surprises from the server...
|
271
271
|
|
272
|
-
$ ruby -d
|
272
|
+
$ ruby -d -Ilib test/myapp.rb
|
273
273
|
Runtime inspection available at localhost:56789
|
274
274
|
Connection established with 127.0.0.1:45689
|
275
275
|
Executing [3]: start_foo
|
@@ -297,6 +297,40 @@ No surprises from the server...
|
|
297
297
|
Cleaning up 127.0.0.1:45689
|
298
298
|
Closed connection from 127.0.0.1:45689
|
299
299
|
|
300
|
+
==== Inserting RTI Dynamically
|
301
|
+
|
302
|
+
Now, imagine that you have a Ruby process running that did not require
|
303
|
+
or load RTI on its own. Here's how to insert it live without stopping
|
304
|
+
and restarting the process. First, we start up a process without RTI...
|
305
|
+
|
306
|
+
$ ruby -d -Ilib test/myapp.rb noload
|
307
|
+
Running as 26061
|
308
|
+
|
309
|
+
...then we use the {rti helper script}[link:files/bin/rti.html] to load
|
310
|
+
in RTI...
|
311
|
+
|
312
|
+
$ bin/rti -load lib 26061
|
313
|
+
Loaded lib/rtinspect.rb in process 26061
|
314
|
+
|
315
|
+
...which does this in the myapp.rb process...
|
316
|
+
|
317
|
+
Running as 26061
|
318
|
+
true
|
319
|
+
Runtime inspection available at localhost:56789
|
320
|
+
#<RuntimeInspection::Thread:0xb79f9874 sleep []>
|
321
|
+
|
322
|
+
...and now we telnet to RTI just like the previous examples...
|
323
|
+
|
324
|
+
$ telnet localhost 56789
|
325
|
+
Trying 127.0.0.1...
|
326
|
+
Connected to localhost.
|
327
|
+
Escape character is '^]'.
|
328
|
+
|
329
|
+
myapp:001:0> local_variables
|
330
|
+
=> ["a", "b", "rti"]
|
331
|
+
|
332
|
+
myapp:002:0>
|
333
|
+
|
300
334
|
= RTI Use
|
301
335
|
|
302
336
|
RTI can be distributed via the same kind of terms as the {Ruby
|
data/TODO
CHANGED
@@ -21,16 +21,27 @@
|
|
21
21
|
* The binding should probably be part of the client connection state
|
22
22
|
and not global to the main thread.
|
23
23
|
|
24
|
-
* Cleanup not happening when client exits while at a stoppoint.
|
25
|
-
|
26
|
-
* Add RuntimeInspectionThread.on_signal method to self-invoke when
|
27
|
-
given a signal (toggle).
|
28
|
-
|
29
|
-
* Add helper to default output into a file.
|
30
|
-
|
31
24
|
* Add support to open any port but report it to a handler (so it can
|
32
25
|
be logged or noted somewhere).
|
33
26
|
|
34
27
|
* Write a helper client to provide ease-of-use features like command
|
35
28
|
completion, command history, command-line editing, etc
|
36
29
|
(i.e. readline).
|
30
|
+
|
31
|
+
* Maybe have a way to explicitly mark objects that are "traceable" so
|
32
|
+
that the user can optionally speed up performance so that
|
33
|
+
handle_tracing() will only look at objects that are marked and
|
34
|
+
immediately skip through for any others. Used in conjunction with
|
35
|
+
get_object(), this could be done dynamically (a. get_object; b. mark
|
36
|
+
object; c. add breakpoint; d. start tracing).
|
37
|
+
|
38
|
+
* Add comparison of RTI with ruby-breakpoint, ruby-debug, and "ruby
|
39
|
+
--debug" to README.
|
40
|
+
|
41
|
+
* Move handle_tracing into the Tracer.
|
42
|
+
|
43
|
+
* Add support for bp_disable.
|
44
|
+
|
45
|
+
* Add optional support for showing source-code at breakpoints (perhaps
|
46
|
+
managed only by the rti client so we don't bog down the rti
|
47
|
+
library).
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
RuntimeInspectionThread v0.0.19
|
data/bin/rti
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This is a helper script for working with a process that either needs
|
4
|
+
# a RuntimeInspection::Thread or has one.
|
5
|
+
#
|
6
|
+
# Copyright (c) 2006-2007 Brad Robel-Forrest.
|
7
|
+
#
|
8
|
+
# This software can be distributed under the terms of the Ruby license
|
9
|
+
# as detailed in the accompanying LICENSE[link:files/LICENSE.html]
|
10
|
+
# file.
|
11
|
+
|
12
|
+
require 'pathname'
|
13
|
+
|
14
|
+
def gdb_file( path, port )
|
15
|
+
require 'tempfile'
|
16
|
+
|
17
|
+
if port
|
18
|
+
args = "(#{port})"
|
19
|
+
end
|
20
|
+
|
21
|
+
f = Tempfile.new( 'rti_' )
|
22
|
+
f.write <<EOF
|
23
|
+
define eval
|
24
|
+
call(rb_p(rb_eval_string_protect($arg0,(int*)0)))
|
25
|
+
end
|
26
|
+
eval("load '#{path.realpath}'")
|
27
|
+
eval("RuntimeInspection::Thread.new#{args}")
|
28
|
+
EOF
|
29
|
+
f.flush
|
30
|
+
return f
|
31
|
+
end
|
32
|
+
|
33
|
+
if ARGV.empty?
|
34
|
+
$stderr.puts "usage: rti [-load <path_to_rtinspect> <pid>] [<port>]"
|
35
|
+
exit 1
|
36
|
+
end
|
37
|
+
|
38
|
+
if i = ARGV.index( '-load' )
|
39
|
+
ARGV.delete_at(i)
|
40
|
+
path = Pathname.new( ARGV.delete_at(i) )
|
41
|
+
pid = ARGV.delete_at(i)
|
42
|
+
port = ARGV.shift
|
43
|
+
|
44
|
+
if path.directory?
|
45
|
+
path += 'rtinspect.rb'
|
46
|
+
end
|
47
|
+
|
48
|
+
unless path.exist?
|
49
|
+
$stderr.puts "Unable to find #{path}"
|
50
|
+
exit 1
|
51
|
+
end
|
52
|
+
|
53
|
+
f = gdb_file( path, port )
|
54
|
+
begin
|
55
|
+
unless system( "gdb -n --batch-silent --pid #{pid} -x #{f.path}" )
|
56
|
+
$stderr.puts "Unable to load #{path} in process #{pid}"
|
57
|
+
exit 1
|
58
|
+
end
|
59
|
+
ensure
|
60
|
+
f.close
|
61
|
+
end
|
62
|
+
|
63
|
+
puts "Loaded #{path} in process #{pid}"
|
64
|
+
end
|
65
|
+
|
66
|
+
# use expect to wait for the prompt (like in the unit tests, but less
|
67
|
+
# strict)
|
68
|
+
|
69
|
+
# sock = TCPSocket.new( 'localhost', port )
|
data/lib/rti/manager.rb
ADDED
@@ -0,0 +1,411 @@
|
|
1
|
+
# Contains the RuntimeInspection::RTIManager.
|
2
|
+
#
|
3
|
+
# Copyright (c) 2006-2007 Brad Robel-Forrest.
|
4
|
+
#
|
5
|
+
# This software can be distributed under the terms of the Ruby license
|
6
|
+
# as detailed in the accompanying LICENSE[link:files/LICENSE.html]
|
7
|
+
# file.
|
8
|
+
|
9
|
+
#
|
10
|
+
module RuntimeInspection
|
11
|
+
|
12
|
+
# This will be the list of all public instance methods that start
|
13
|
+
# with 'rti_'. These methods are callable from a client's
|
14
|
+
# connection to invoke various helpers remotely (e.g. manipulating
|
15
|
+
# breakpoints as well as other helpful functions). The first
|
16
|
+
# argument to these will always be the state. Remaining arguments
|
17
|
+
# will be whatever strings were obtained from the client.
|
18
|
+
#
|
19
|
+
class RTIManager
|
20
|
+
|
21
|
+
# If this character is used as the first non-whitespace value
|
22
|
+
# in a command, the remaining data will be used to invoke an
|
23
|
+
# RTIManager method directly (outside the eval scope).
|
24
|
+
#
|
25
|
+
CMD_MARKER = ?.
|
26
|
+
|
27
|
+
# Track the list of available commands.
|
28
|
+
#
|
29
|
+
RTI_METHODS = []
|
30
|
+
|
31
|
+
# Used to collect data regarding each breakpoint. The state is
|
32
|
+
# associated with the appropriate client that is waiting for
|
33
|
+
# this breakpoint to be hit.
|
34
|
+
#
|
35
|
+
BreakPoint = Struct.new( :id, :state )
|
36
|
+
|
37
|
+
# Collect all methods that we are willing to expose to a
|
38
|
+
# caller.
|
39
|
+
#
|
40
|
+
def self.method_added( id )
|
41
|
+
RTI_METHODS << id.id2name
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get a new RTIManager
|
45
|
+
#
|
46
|
+
def initialize( breakpoints, tracing_proc, state )
|
47
|
+
@breakpoints = breakpoints
|
48
|
+
@tracing_proc = tracing_proc
|
49
|
+
@state = state
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get the list of RTI commands available.
|
53
|
+
#
|
54
|
+
def list
|
55
|
+
RTI_METHODS.sort
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get the state information for this client.
|
59
|
+
#
|
60
|
+
def state
|
61
|
+
@state
|
62
|
+
end
|
63
|
+
|
64
|
+
# Given a name, return the real class object. Returns the
|
65
|
+
# first one found unless an explicit "path" is provided via ::
|
66
|
+
# designators.
|
67
|
+
#
|
68
|
+
def name2class( name )
|
69
|
+
if name.include?( '::' )
|
70
|
+
ObjectSpace.each_object( Class ) do |c|
|
71
|
+
return c if name == c.name
|
72
|
+
end
|
73
|
+
else
|
74
|
+
ObjectSpace.each_object( Class ) do |c|
|
75
|
+
return c if c.name.match( /#{name}$/ )
|
76
|
+
end
|
77
|
+
end
|
78
|
+
return nil
|
79
|
+
end
|
80
|
+
|
81
|
+
# Very simple helper to assist caller in retrieving a specific
|
82
|
+
# object. This will return the first one found. Anything
|
83
|
+
# requiring more complicated decision logic about what object
|
84
|
+
# to return should use ObjectSpace directly.
|
85
|
+
#
|
86
|
+
# +name+:: The object's class or name (uses #name2class for
|
87
|
+
# the latter).
|
88
|
+
#
|
89
|
+
def get_object( name )
|
90
|
+
if name.kind_of? String
|
91
|
+
return nil unless c = name2class(name)
|
92
|
+
else
|
93
|
+
c = name
|
94
|
+
end
|
95
|
+
ObjectSpace.each_object( c ) {|obj| return obj}
|
96
|
+
return nil
|
97
|
+
end
|
98
|
+
|
99
|
+
# Stop all other threads except the
|
100
|
+
# RuntimeInsepction::Thread. Be careful. This will stop any
|
101
|
+
# new connections as well.
|
102
|
+
#
|
103
|
+
def stop_world
|
104
|
+
::Thread.critical = true
|
105
|
+
end
|
106
|
+
|
107
|
+
# Start all other threads up again.
|
108
|
+
#
|
109
|
+
def start_world
|
110
|
+
::Thread.critical = false
|
111
|
+
end
|
112
|
+
|
113
|
+
# Convenience method for what is essentially caller. The
|
114
|
+
# argument specifies the number of calls to report.
|
115
|
+
#
|
116
|
+
def backtrace( calls=5 )
|
117
|
+
caller[1..calls]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Convenience method for listing threads in the system. The
|
121
|
+
# first value reported is the thread's object_id which can be
|
122
|
+
# used in bp_attach and bp_add. Only threads that do not have
|
123
|
+
# :thread_name keys that start with 'rti_' are shown.
|
124
|
+
#
|
125
|
+
def threads
|
126
|
+
tl = Thread.list.collect do |th|
|
127
|
+
unless( th[:thread_name].to_s.match( /^rti_/ ) or
|
128
|
+
th.kind_of?( Thread ))
|
129
|
+
th
|
130
|
+
end
|
131
|
+
end
|
132
|
+
tl.compact!
|
133
|
+
return tl
|
134
|
+
end
|
135
|
+
|
136
|
+
# Expression for splitting apart a user-supplied BreakPoint in
|
137
|
+
# bp_add.
|
138
|
+
#
|
139
|
+
BP_RE = Regexp.new( /^((.+):(\d+)|((.+)\#)?(.*?))(>(.+))?$/ )
|
140
|
+
|
141
|
+
# Add a new BreakPoint. Execution will not stop at any
|
142
|
+
# BreakPoints until bp_start is called. The argument
|
143
|
+
# should use one of the following forms:
|
144
|
+
#
|
145
|
+
# * Class#method
|
146
|
+
# * Module#method
|
147
|
+
# * method
|
148
|
+
# * file:line
|
149
|
+
# * Append >thread to any of the above
|
150
|
+
#
|
151
|
+
# In the first three forms, the BreakPoint will stop on the
|
152
|
+
# 'call' event into that method.
|
153
|
+
#
|
154
|
+
# The last form enables a BreakPoint to be declared for a
|
155
|
+
# particular thread. The thread value may either be an
|
156
|
+
# object_id (see #threads) or it's name as defined by the
|
157
|
+
# thread's :thread_name key.
|
158
|
+
#
|
159
|
+
# Once a BreakPoint is stopped in a thread, bp_finish,
|
160
|
+
# bp_next, and bp_step will act inside that thread. When
|
161
|
+
# bp_continue is called, the next BreakPoint to be hit
|
162
|
+
# regardless of the thread will be the StopPoint.
|
163
|
+
#
|
164
|
+
# Note that even though a particular sequence of StopPoints is
|
165
|
+
# thread-specific, that doesn't mean that some other generic
|
166
|
+
# BreakPoint might be hit and stop processing elsewhere. This
|
167
|
+
# can be mitigated by using the thread-specific BreakPoints.
|
168
|
+
#
|
169
|
+
def bp_add( key )
|
170
|
+
if bp = @breakpoints[key]
|
171
|
+
return "Breakpoint already held as #{bp.id} by " +
|
172
|
+
"#{bp.state.socket}"
|
173
|
+
end
|
174
|
+
|
175
|
+
unless m = key.match( BP_RE )
|
176
|
+
return "Unable to parse #{key}"
|
177
|
+
end
|
178
|
+
|
179
|
+
file = m[2]
|
180
|
+
line = m[3]
|
181
|
+
cont = m[5]
|
182
|
+
meth = m[6]
|
183
|
+
thread = m[8]
|
184
|
+
|
185
|
+
if meth and meth.empty?
|
186
|
+
meth = nil
|
187
|
+
end
|
188
|
+
|
189
|
+
if cont
|
190
|
+
ObjectSpace.each_object( Class ) do |c|
|
191
|
+
if c.name == cont
|
192
|
+
cont = c
|
193
|
+
break
|
194
|
+
end
|
195
|
+
end
|
196
|
+
if cont.kind_of?( String )
|
197
|
+
ObjectSpace.each_object( Module ) do |m|
|
198
|
+
if m.name == cont
|
199
|
+
cont = m
|
200
|
+
break
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
if cont.kind_of?( String )
|
205
|
+
return "Unable to find matching module or class " +
|
206
|
+
"for #{cont}"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
if meth
|
211
|
+
if cont
|
212
|
+
unless( cont.instance_methods.include?( meth ) or
|
213
|
+
cont.methods.include?( meth ))
|
214
|
+
return "Unable to find #{meth} method in #{cont}"
|
215
|
+
end
|
216
|
+
else
|
217
|
+
ObjectSpace.each_object( Class ) do |c|
|
218
|
+
if( c.instance_methods.include?( meth ) or
|
219
|
+
c.methods.include?( meth ))
|
220
|
+
cont = c
|
221
|
+
break
|
222
|
+
end
|
223
|
+
end
|
224
|
+
unless cont
|
225
|
+
ObjectSpace.each_object( Module ) do |m|
|
226
|
+
if( m.instance_methods.include?( meth ) or
|
227
|
+
m.methods.include?( meth ))
|
228
|
+
cont = m
|
229
|
+
break
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
unless cont
|
234
|
+
return "Unable to find containing class or module " +
|
235
|
+
"for #{meth}"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
if thread
|
241
|
+
key.sub!( />.+$/, ">#{get_thread_id(thread)}" )
|
242
|
+
end
|
243
|
+
|
244
|
+
return real_bp_add( key )
|
245
|
+
end
|
246
|
+
|
247
|
+
# Delete a breakpoint.
|
248
|
+
#
|
249
|
+
def bp_delete( id )
|
250
|
+
id = id.to_i
|
251
|
+
ret = "Unable to find breakpoint #{id}"
|
252
|
+
@breakpoints.delete_if do |key, bp|
|
253
|
+
if bp.id == id and bp.state == @state
|
254
|
+
ret = "Deleted breakpoint #{id}"
|
255
|
+
true
|
256
|
+
end
|
257
|
+
end
|
258
|
+
return ret
|
259
|
+
end
|
260
|
+
|
261
|
+
# List all the breakpoints.
|
262
|
+
#
|
263
|
+
def bp_list( show_all=false )
|
264
|
+
ordered = @breakpoints.sort do |a, b|
|
265
|
+
a[1].id <=> b[1].id
|
266
|
+
end
|
267
|
+
ordered.collect do |key, bp|
|
268
|
+
next unless show_all or bp.state == @state
|
269
|
+
if show_all and bp.state != @state
|
270
|
+
post = " (#{bp.state.socket})"
|
271
|
+
end
|
272
|
+
"#{bp.id}: #{key}#{post}"
|
273
|
+
end.compact
|
274
|
+
end
|
275
|
+
|
276
|
+
# Start tracing looking for breakpoints to stop at.
|
277
|
+
#
|
278
|
+
def bp_start
|
279
|
+
if @breakpoints.empty?
|
280
|
+
return "No breakpoints set"
|
281
|
+
end
|
282
|
+
bp_stop
|
283
|
+
set_trace_func( @tracing_proc )
|
284
|
+
@state.bp_portal = @state.bp_waiting.pop
|
285
|
+
return nil
|
286
|
+
end
|
287
|
+
|
288
|
+
# Stop tracing for breakpoints.
|
289
|
+
#
|
290
|
+
def bp_stop
|
291
|
+
unless @state.bp_portal
|
292
|
+
return "Not stopped at a breakpoint"
|
293
|
+
end
|
294
|
+
@state.bp_portal.cmds << :stop
|
295
|
+
@state.delete( :bp_portal )
|
296
|
+
return nil
|
297
|
+
end
|
298
|
+
|
299
|
+
# Continue tracing waiting for the very next call inside the
|
300
|
+
# specified thread. The thread value to use is either the
|
301
|
+
# thread's name (as available in the thread's :thread_name
|
302
|
+
# key) or the thread's object_id.
|
303
|
+
#
|
304
|
+
def bp_attach( thread )
|
305
|
+
# Need to stop any current tracing that may be running.
|
306
|
+
bp_stop
|
307
|
+
ret = bp_add( ">#{thread}" )
|
308
|
+
unless ret.match( /^Added / )
|
309
|
+
return ret
|
310
|
+
end
|
311
|
+
bp_start
|
312
|
+
end
|
313
|
+
|
314
|
+
# Continue tracing from current position waiting for next
|
315
|
+
# breakpoint to hit.
|
316
|
+
#
|
317
|
+
def bp_continue
|
318
|
+
unless @state.bp_portal
|
319
|
+
return "Not stopped at a breakpoint"
|
320
|
+
end
|
321
|
+
@state.bp_portal.cmds << :continue
|
322
|
+
@state.bp_portal = @state.bp_waiting.pop
|
323
|
+
return nil
|
324
|
+
end
|
325
|
+
|
326
|
+
# Finish the current frame and stop in the return.
|
327
|
+
#
|
328
|
+
def bp_finish
|
329
|
+
unless @state.bp_portal
|
330
|
+
return "Not stopped at a breakpoint"
|
331
|
+
end
|
332
|
+
@state.bp_portal.cmds << :fin
|
333
|
+
@state.bp_portal = @state.bp_waiting.pop
|
334
|
+
return nil
|
335
|
+
end
|
336
|
+
|
337
|
+
# Break execution at the next line (without going into another
|
338
|
+
# class).
|
339
|
+
#
|
340
|
+
def bp_next
|
341
|
+
unless @state.bp_portal
|
342
|
+
return "Not stopped at a breakpoint"
|
343
|
+
end
|
344
|
+
@state.bp_portal.cmds << :next
|
345
|
+
@state.bp_portal = @state.bp_waiting.pop
|
346
|
+
return nil
|
347
|
+
end
|
348
|
+
|
349
|
+
# Follow the next instruction--potentially into another class
|
350
|
+
# and/or method.
|
351
|
+
#
|
352
|
+
def bp_step
|
353
|
+
unless @state.bp_portal
|
354
|
+
return "Not stopped at a breakpoint"
|
355
|
+
end
|
356
|
+
@state.bp_portal.cmds << :step
|
357
|
+
@state.bp_portal = @state.bp_waiting.pop
|
358
|
+
return nil
|
359
|
+
end
|
360
|
+
|
361
|
+
private
|
362
|
+
|
363
|
+
def get_thread_id( thread )
|
364
|
+
if thread.kind_of?( String )
|
365
|
+
if thread[0] == ?:
|
366
|
+
thread = thread[1..-1].to_sym
|
367
|
+
else
|
368
|
+
id = thread.to_i
|
369
|
+
if id != 0
|
370
|
+
thread = id
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
::Thread.list.each do |th|
|
375
|
+
if thread.kind_of?( Fixnum )
|
376
|
+
if th.object_id == thread
|
377
|
+
thread = th
|
378
|
+
break
|
379
|
+
end
|
380
|
+
else
|
381
|
+
if th[:thread_name] == thread
|
382
|
+
thread = th
|
383
|
+
break
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
unless thread.kind_of?( ::Thread )
|
388
|
+
raise "Unable to locate thread #{thread.inspect}"
|
389
|
+
end
|
390
|
+
return thread.object_id
|
391
|
+
end
|
392
|
+
|
393
|
+
def real_bp_add( key )
|
394
|
+
@state.bp_waiting ||= Queue.new
|
395
|
+
|
396
|
+
@state.breakpoint_id ||= 0
|
397
|
+
@state.breakpoint_id += 1
|
398
|
+
|
399
|
+
@breakpoints[key] = BreakPoint.new( @state.breakpoint_id, @state )
|
400
|
+
|
401
|
+
return "Added breakpoint #{@state.breakpoint_id}"
|
402
|
+
end
|
403
|
+
|
404
|
+
# Now toss the private methods from our list...
|
405
|
+
private_instance_methods.each do |m|
|
406
|
+
RTI_METHODS.delete( m )
|
407
|
+
end
|
408
|
+
|
409
|
+
end
|
410
|
+
|
411
|
+
end
|