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