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 CHANGED
@@ -50,16 +50,16 @@ thread's inspection abilities.
50
50
  Connected to localhost.
51
51
  Escape character is '^]'.
52
52
 
53
- rtinspect:001:0> local_variables
53
+ myapp:001:0> local_variables
54
54
  => ["rti"]
55
55
 
56
- rtinspect:002:0> rti.state
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
- rtinspect:003:0> rti.state.use_yaml = true
59
+ myapp:003:0> rti.state.use_yaml = true
60
60
  => --- true
61
61
 
62
- rtinspect:004:0> rti.state
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
- rtinspect:005:0> rti.state.block_count = 1
79
+ myapp:005:0> rti.state.block_count = 1
80
80
  => --- 1
81
81
 
82
- rtinspect:006:1> 2.times do
83
- rtinspect:007:1> |i|
84
- rtinspect:008:1> p i
85
- rtinspect:009:1> end
86
- rtinspect:010:1>
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
- rtinspect:011:2> exit
92
- rtinspect:012:2>
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
- rtinspect:013:3> ^]
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 rtinspect.rb
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
- rtinspect:001:0> .bp_add Foo#bar
145
+ myapp:001:0> .bp_add Foo#bar
146
146
  => "Added breakpoint 1"
147
147
 
148
- rtinspect:002:0> start_foo
148
+ myapp:002:0> start_foo
149
149
  => #<Thread:0xb7d22974 sleep>
150
150
 
151
- rtinspect:003:0> .bp_start
151
+ myapp:003:0> .bp_start
152
152
  => nil
153
153
 
154
- Breakpoint 1 in Foo#bar from rtinspect.rb:919 (call)
155
- rtinspect:004:0> local_variables
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 rtinspect.rb:919 (call)
159
- rtinspect:005:0> b
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 rtinspect.rb:919 (call)
163
- rtinspect:006:0> b = 1003
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 rtinspect.rb:919 (call)
167
- rtinspect:007:0> @a
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 rtinspect.rb:919 (call)
171
- rtinspect:008:0> @a = 11
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 rtinspect.rb:919 (call)
175
- rtinspect:009:0> .bp_next
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 rtinspect.rb:920 (line)
179
- rtinspect:010:0> .bp_continue
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 rtinspect.rb:919 (call)
183
- rtinspect:011:0> b
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 rtinspect.rb:919 (call)
187
- rtinspect:012:0> p @a
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 rtinspect.rb:919 (call)
192
- rtinspect:013:0> .bp_stop
191
+ Breakpoint 1 in Foo#bar from myapp.rb:10 (call)
192
+ myapp:013:0> .bp_stop
193
193
  => nil
194
194
 
195
- rtinspect:014:0> ^]
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 rtinspect.rb
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
- rtinspect:001:0> start_foo
246
+ myapp:001:0> start_foo
247
247
  => #<Thread:0xb7d11d04 sleep>
248
248
 
249
- rtinspect:002:0> f = rti.get_object('Foo')
249
+ myapp:002:0> f = rti.get_object('Foo')
250
250
  => #<Foo:0xb7d116b0 @a=2>
251
251
 
252
- rtinspect:003:0> rti.state.block_count = 1
252
+ myapp:003:0> rti.state.block_count = 1
253
253
  => 1
254
254
 
255
- rtinspect:004:1> def f.seeit
256
- rtinspect:005:1> @a
257
- rtinspect:006:1> end
258
- rtinspect:007:1>
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
- rtinspect:008:2> f.seeit
262
- rtinspect:009:2>
261
+ myapp:008:2> f.seeit
262
+ myapp:009:2>
263
263
  => 11
264
264
 
265
- rtinspect:010:3> ^]
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 rtinspect.rb
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 )
@@ -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