rtinspect 0.0.1 → 0.0.19
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.
- 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
|