rtinspect 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +340 -0
- data/LICENSE +53 -0
- data/README +331 -0
- data/TODO +36 -0
- data/lib/rtinspect.rb +950 -0
- data/test/test.rb +162 -0
- metadata +54 -0
data/README
ADDED
@@ -0,0 +1,331 @@
|
|
1
|
+
= What is this?
|
2
|
+
|
3
|
+
Are you a Ruby enthusiast pining for the days of yore when you could
|
4
|
+
fire up a debugger to walk through and learn a new piece of code? Do
|
5
|
+
you ever find yourself desperately wishing you could attach to a Ruby
|
6
|
+
process and find out what values are hanging around in a method call
|
7
|
+
that doesn't seem to be working properly? Or, perhaps you're somebody
|
8
|
+
that is tired of the "add a puts call - run the process - test the
|
9
|
+
problem" cycle? If any of this sounds familiar then making use of a
|
10
|
+
RuntimeInspectionThread is for you.
|
11
|
+
|
12
|
+
An RTI thread exposes remote access into a running Ruby process. Once
|
13
|
+
instantiated, you can telnet in and issue strings that will be passed
|
14
|
+
off to eval with the results printed back to the telnet session. The
|
15
|
+
code evaluation can either occur in a generic binding as defined by
|
16
|
+
the main thread's instantiation or the evaluation can be run from
|
17
|
+
within a given breakpoint--not unlike using the Ruby debugger. Thus,
|
18
|
+
you have the full power of inspection as well as the ability to break
|
19
|
+
into a thread and carefully step through logic, inspecting local
|
20
|
+
variables and such along the way. RTI even provides some helpful
|
21
|
+
lookup methods that can be accessed to obtain and inspect various
|
22
|
+
objects instantiated in the process you are trying to learn and/or
|
23
|
+
debug.
|
24
|
+
|
25
|
+
Also note that there are other ways to help you work with and debug
|
26
|
+
Ruby processes.
|
27
|
+
|
28
|
+
* Use GDB to attach to a process and issue Ruby commands. This is very
|
29
|
+
useful in those cases where Ruby itself is spinning, if a C
|
30
|
+
extension is having problems, or if you'd just like to see where the
|
31
|
+
Ruby or C call stack is at a given point in time. There are some very
|
32
|
+
handy helpers for making this level of interaction easier.
|
33
|
+
|
34
|
+
* {GDB macros}[http://eigenclass.org/hiki.rb?ruby+live+process+introspection]
|
35
|
+
|
36
|
+
* {GDB Ruby interface}[http://weblog.jamisbuck.org/2006/9/25/gdb-wrapper-for-ruby]
|
37
|
+
|
38
|
+
* Statically insert breakpoints in your code using
|
39
|
+
{ruby-breakpoint}[http://rubyforge.org/projects/ruby-breakpoint] and
|
40
|
+
IRB.
|
41
|
+
|
42
|
+
==== General Inspection Example
|
43
|
+
|
44
|
+
The following is an example session from the client's perspective to
|
45
|
+
give you an very simple sampling of some of the capabilities of an RTI
|
46
|
+
thread's inspection abilities.
|
47
|
+
|
48
|
+
$ telnet localhost 56789
|
49
|
+
Trying 127.0.0.1...
|
50
|
+
Connected to localhost.
|
51
|
+
Escape character is '^]'.
|
52
|
+
|
53
|
+
rtinspect:001:0> local_variables
|
54
|
+
=> ["rti"]
|
55
|
+
|
56
|
+
rtinspect:002:0> rti.state
|
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
|
+
|
59
|
+
rtinspect:003:0> rti.state.use_yaml = true
|
60
|
+
=> --- true
|
61
|
+
|
62
|
+
rtinspect:004:0> rti.state
|
63
|
+
=> --- &id001 !map:RuntimeInspectionThread::State
|
64
|
+
:socket: !ruby/object:TCPSocket
|
65
|
+
peerstr: 127.0.0.1:48720
|
66
|
+
:rti: !ruby/object:RuntimeInspectionThread::RTIManager
|
67
|
+
breakpoints: {}
|
68
|
+
|
69
|
+
state: *id001
|
70
|
+
tracing_proc: !ruby/object:Proc {}
|
71
|
+
|
72
|
+
:cmd_count: 4
|
73
|
+
:safe_level: 3
|
74
|
+
:block_count: 0
|
75
|
+
:block_cmd: ""
|
76
|
+
:use_yaml: true
|
77
|
+
:eval_timeout: 60
|
78
|
+
|
79
|
+
rtinspect:005:0> rti.state.block_count = 1
|
80
|
+
=> --- 1
|
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>
|
87
|
+
0
|
88
|
+
1
|
89
|
+
=> --- 2
|
90
|
+
|
91
|
+
rtinspect:011:2> exit
|
92
|
+
rtinspect:012:2>
|
93
|
+
=> --- !ruby/exception:SystemExit
|
94
|
+
message: "(eval):1:in `exit': exit"
|
95
|
+
|
96
|
+
rtinspect:013:3> ^]
|
97
|
+
|
98
|
+
telnet> quit
|
99
|
+
Connection closed.
|
100
|
+
|
101
|
+
Note, after the block mode was enabled, an empty line was necessary
|
102
|
+
for the evaluation of the code to happen. The final _exit_ command is
|
103
|
+
actually going to try to call _exit_ in the remote process. Because we
|
104
|
+
run with a default $SAFE level of 3, this action is denied and an
|
105
|
+
exception is raised, caught, and shown to the remote client.
|
106
|
+
|
107
|
+
There are two numbers shown in the prompt. The first is simply the
|
108
|
+
"line" number of the current command. It is an ever increasing value
|
109
|
+
for every input received from the client. The second is the block
|
110
|
+
count. Normally, this is zero indicating each line is a full command
|
111
|
+
to evaluate. However, when the block count is non-zero, this second
|
112
|
+
value increases each time a full block is evaluated.
|
113
|
+
|
114
|
+
Here's what happened on the server's side (note, it's running with
|
115
|
+
debug enabled so we can see the details):
|
116
|
+
|
117
|
+
$ ruby -d rtinspect.rb
|
118
|
+
Runtime inspection available at localhost:56789
|
119
|
+
Connection established with 127.0.0.1:48720
|
120
|
+
Executing [3]: local_variables
|
121
|
+
Executing [3]: rti.state
|
122
|
+
Executing [3]: rti.state.use_yaml = true
|
123
|
+
Executing [3]: rti.state
|
124
|
+
Executing [3]: rti.state.block_count = 1
|
125
|
+
Executing [3]: 2.times do
|
126
|
+
|i|
|
127
|
+
p i
|
128
|
+
end
|
129
|
+
Executing [3]: exit
|
130
|
+
Cleaning up 127.0.0.1:48720
|
131
|
+
Closed connection from 127.0.0.1:48720
|
132
|
+
|
133
|
+
==== Breakpoint Example
|
134
|
+
|
135
|
+
Stopping another thread's execution can serve to be very useful to
|
136
|
+
track how the program operates as well as to inspect any local values
|
137
|
+
for erroneous data. From the client's perspective, the following is
|
138
|
+
what might be expected.
|
139
|
+
|
140
|
+
$ telnet localhost 56789
|
141
|
+
Trying 127.0.0.1...
|
142
|
+
Connected to localhost.
|
143
|
+
Escape character is '^]'.
|
144
|
+
|
145
|
+
rtinspect:001:0> .bp_add Foo#bar
|
146
|
+
=> "Added breakpoint 1"
|
147
|
+
|
148
|
+
rtinspect:002:0> start_foo
|
149
|
+
=> #<Thread:0xb7d22974 sleep>
|
150
|
+
|
151
|
+
rtinspect:003:0> .bp_start
|
152
|
+
=> nil
|
153
|
+
|
154
|
+
Breakpoint 1 in Foo#bar from rtinspect.rb:919 (call)
|
155
|
+
rtinspect:004:0> local_variables
|
156
|
+
=> ["b", "rti"]
|
157
|
+
|
158
|
+
Breakpoint 1 in Foo#bar from rtinspect.rb:919 (call)
|
159
|
+
rtinspect:005:0> b
|
160
|
+
=> nil
|
161
|
+
|
162
|
+
Breakpoint 1 in Foo#bar from rtinspect.rb:919 (call)
|
163
|
+
rtinspect:006:0> b = 1003
|
164
|
+
=> 1003
|
165
|
+
|
166
|
+
Breakpoint 1 in Foo#bar from rtinspect.rb:919 (call)
|
167
|
+
rtinspect:007:0> @a
|
168
|
+
=> 3
|
169
|
+
|
170
|
+
Breakpoint 1 in Foo#bar from rtinspect.rb:919 (call)
|
171
|
+
rtinspect:008:0> @a = 11
|
172
|
+
=> 11
|
173
|
+
|
174
|
+
Breakpoint 1 in Foo#bar from rtinspect.rb:919 (call)
|
175
|
+
rtinspect:009:0> .bp_next
|
176
|
+
=> nil
|
177
|
+
|
178
|
+
Breakpoint 1 in Foo#bar from rtinspect.rb:920 (line)
|
179
|
+
rtinspect:010:0> .bp_continue
|
180
|
+
=> nil
|
181
|
+
|
182
|
+
Breakpoint 1 in Foo#bar from rtinspect.rb:919 (call)
|
183
|
+
rtinspect:011:0> b
|
184
|
+
=> nil
|
185
|
+
|
186
|
+
Breakpoint 1 in Foo#bar from rtinspect.rb:919 (call)
|
187
|
+
rtinspect:012:0> p @a
|
188
|
+
12
|
189
|
+
=> nil
|
190
|
+
|
191
|
+
Breakpoint 1 in Foo#bar from rtinspect.rb:919 (call)
|
192
|
+
rtinspect:013:0> .bp_stop
|
193
|
+
=> nil
|
194
|
+
|
195
|
+
rtinspect:014:0> ^]
|
196
|
+
|
197
|
+
telnet> quit
|
198
|
+
Connection closed.
|
199
|
+
|
200
|
+
Note that values can be altered in addition to examined. The "period"
|
201
|
+
marker is used to denote actions that can be executed directly in the
|
202
|
+
RTI object itself, instead of running through an eval. This makes some
|
203
|
+
commands easier to work with without having to escape much of the data
|
204
|
+
to pass through. For example, the breakpoint could have been added via
|
205
|
+
a command like "rti.bp_add 'Foo#bar'" to achieve the same effect.
|
206
|
+
|
207
|
+
And from the server's perspective (running in debug mode to see the
|
208
|
+
details), after the call to start_foo is made, the Foo#bar object
|
209
|
+
begins printing out the value of its member variable, @a. Notice that
|
210
|
+
the value jumps from 2 to 11 after the breakpoint is hit and the
|
211
|
+
client has adjusted the value.
|
212
|
+
|
213
|
+
$ ruby -d rtinspect.rb
|
214
|
+
Runtime inspection available at localhost:56789
|
215
|
+
Connection established with 127.0.0.1:40202
|
216
|
+
Executing [3]: start_foo
|
217
|
+
0
|
218
|
+
1
|
219
|
+
2
|
220
|
+
Executing (explicit binding) [3]: local_variables
|
221
|
+
Executing (explicit binding) [3]: b
|
222
|
+
Executing (explicit binding) [3]: b = 1003
|
223
|
+
Executing (explicit binding) [3]: @a
|
224
|
+
Executing (explicit binding) [3]: @a = 11
|
225
|
+
11
|
226
|
+
Executing (explicit binding) [3]: b
|
227
|
+
Executing (explicit binding) [3]: p @a
|
228
|
+
12
|
229
|
+
Cleaning up 127.0.0.1:40202
|
230
|
+
Closed connection from 127.0.0.1:40202
|
231
|
+
13
|
232
|
+
14
|
233
|
+
|
234
|
+
==== Finding and Inspecting Other Objects Example
|
235
|
+
|
236
|
+
It may be unnecessary to halt a thread in a breakpoint. In many cases,
|
237
|
+
simple examination of a particular object is needed. The following is
|
238
|
+
a straightforward case that looks for a specific object and inspects
|
239
|
+
it using methods added to the object dynamically.
|
240
|
+
|
241
|
+
$ telnet localhost 56789
|
242
|
+
Trying 127.0.0.1...
|
243
|
+
Connected to localhost.
|
244
|
+
Escape character is '^]'.
|
245
|
+
|
246
|
+
rtinspect:001:0> start_foo
|
247
|
+
=> #<Thread:0xb7d11d04 sleep>
|
248
|
+
|
249
|
+
rtinspect:002:0> f = rti.get_object('Foo')
|
250
|
+
=> #<Foo:0xb7d116b0 @a=2>
|
251
|
+
|
252
|
+
rtinspect:003:0> rti.state.block_count = 1
|
253
|
+
=> 1
|
254
|
+
|
255
|
+
rtinspect:004:1> def f.seeit
|
256
|
+
rtinspect:005:1> @a
|
257
|
+
rtinspect:006:1> end
|
258
|
+
rtinspect:007:1>
|
259
|
+
=> nil
|
260
|
+
|
261
|
+
rtinspect:008:2> f.seeit
|
262
|
+
rtinspect:009:2>
|
263
|
+
=> 11
|
264
|
+
|
265
|
+
rtinspect:010:3> ^]
|
266
|
+
|
267
|
+
telnet> quit
|
268
|
+
Connection closed.
|
269
|
+
|
270
|
+
No surprises from the server...
|
271
|
+
|
272
|
+
$ ruby -d rtinspect.rb
|
273
|
+
Runtime inspection available at localhost:56789
|
274
|
+
Connection established with 127.0.0.1:45689
|
275
|
+
Executing [3]: start_foo
|
276
|
+
0
|
277
|
+
1
|
278
|
+
Executing [3]: f = rti.get_object('Foo')
|
279
|
+
2
|
280
|
+
Executing [3]: rti.state.block_count = 1
|
281
|
+
3
|
282
|
+
4
|
283
|
+
5
|
284
|
+
6
|
285
|
+
7
|
286
|
+
8
|
287
|
+
9
|
288
|
+
Executing [3]: def f.seeit
|
289
|
+
@a
|
290
|
+
end
|
291
|
+
10
|
292
|
+
Executing [3]: f.seeit
|
293
|
+
11
|
294
|
+
12
|
295
|
+
13
|
296
|
+
14
|
297
|
+
Cleaning up 127.0.0.1:45689
|
298
|
+
Closed connection from 127.0.0.1:45689
|
299
|
+
|
300
|
+
= RTI Use
|
301
|
+
|
302
|
+
RTI can be distributed via the same kind of terms as the {Ruby
|
303
|
+
license}[link:files/LICENSE.html].
|
304
|
+
|
305
|
+
There are a few simple ways of enabling an RTI thread:
|
306
|
+
|
307
|
+
1. Require the class and enable the thread via a signal.
|
308
|
+
|
309
|
+
2. Require the class and always have the thread running and available.
|
310
|
+
|
311
|
+
3. Run the script in attach mode whereby it leverages GDB to attach to
|
312
|
+
a running process to load itself and start an RTI thread.
|
313
|
+
|
314
|
+
= Tips and Tricks
|
315
|
+
|
316
|
+
* The default binding used in normal (non-breakpoint) evals is kept
|
317
|
+
around. Thus, you can use this to store locals across eval calls.
|
318
|
+
|
319
|
+
* The state information is available as a local to each eval call as
|
320
|
+
simply "rti_state". This means you can do things like
|
321
|
+
"rti_state.use_yaml = true" to adjust various runtime options of the
|
322
|
+
client state or store information to keep across eval calls during a
|
323
|
+
breakpoint eval.
|
324
|
+
|
325
|
+
= Notes/Caveats
|
326
|
+
|
327
|
+
* If two clients are debugging the same thread, only one will get
|
328
|
+
access to it at a time.
|
329
|
+
|
330
|
+
* Only one thread may have request a given breakpoint (i.e. two
|
331
|
+
threads can not both try to stop at Foo#bar).
|
data/TODO
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
* Is it possible to load this class and instantiate it into a running
|
2
|
+
ruby process via gdb? This way it is not necessary to have this as
|
3
|
+
a permanent part of the program.
|
4
|
+
|
5
|
+
- Initially, write up instructions on how this might be done via
|
6
|
+
gdb and the gdbinit helpers.
|
7
|
+
|
8
|
+
- Next, incorporate ability into rtinspect when it is run from the
|
9
|
+
command line.
|
10
|
+
|
11
|
+
- Finally, extract the attach/run logic from gdb into a separate
|
12
|
+
application or embed it into rtinspect.
|
13
|
+
|
14
|
+
* Suppress the inspect output of rti_state or move the client
|
15
|
+
modifiable values to a separate state variable.
|
16
|
+
|
17
|
+
* Document overall scheme and show a typical call pattern to assist
|
18
|
+
readers of the code to understand the layout and ramp up quicker on
|
19
|
+
the design.
|
20
|
+
|
21
|
+
* The binding should probably be part of the client connection state
|
22
|
+
and not global to the main thread.
|
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
|
+
* Add support to open any port but report it to a handler (so it can
|
32
|
+
be logged or noted somewhere).
|
33
|
+
|
34
|
+
* Write a helper client to provide ease-of-use features like command
|
35
|
+
completion, command history, command-line editing, etc
|
36
|
+
(i.e. readline).
|