rtinspect 0.0.1
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/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).
|