rtinspect 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/COPYING +340 -0
  2. data/LICENSE +53 -0
  3. data/README +331 -0
  4. data/TODO +36 -0
  5. data/lib/rtinspect.rb +950 -0
  6. data/test/test.rb +162 -0
  7. 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).