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