rtinspect 0.0.1 → 0.0.19
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/README +84 -50
- data/TODO +18 -7
- data/VERSION +1 -0
- data/bin/rti +69 -0
- data/lib/rti/manager.rb +411 -0
- data/lib/rti/redirect.rb +102 -0
- data/lib/rti/state.rb +65 -0
- data/lib/rti/thread.rb +553 -0
- data/lib/rti/tracer.rb +71 -0
- data/lib/rtinspect.rb +76 -899
- data/test/test.rb +248 -17
- metadata +13 -6
    
        data/README
    CHANGED
    
    | @@ -50,16 +50,16 @@ thread's inspection abilities. | |
| 50 50 | 
             
                Connected to localhost.
         | 
| 51 51 | 
             
                Escape character is '^]'.
         | 
| 52 52 |  | 
| 53 | 
            -
                 | 
| 53 | 
            +
                myapp:001:0> local_variables
         | 
| 54 54 | 
             
                => ["rti"]
         | 
| 55 55 |  | 
| 56 | 
            -
                 | 
| 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 | 
            -
                 | 
| 59 | 
            +
                myapp:003:0> rti.state.use_yaml = true
         | 
| 60 60 | 
             
                => --- true
         | 
| 61 61 |  | 
| 62 | 
            -
                 | 
| 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 | 
            -
                 | 
| 79 | 
            +
                myapp:005:0> rti.state.block_count = 1
         | 
| 80 80 | 
             
                => --- 1
         | 
| 81 81 |  | 
| 82 | 
            -
                 | 
| 83 | 
            -
                 | 
| 84 | 
            -
                 | 
| 85 | 
            -
                 | 
| 86 | 
            -
                 | 
| 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 | 
            -
                 | 
| 92 | 
            -
                 | 
| 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 | 
            -
                 | 
| 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  | 
| 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 | 
            -
                 | 
| 145 | 
            +
                myapp:001:0> .bp_add Foo#bar
         | 
| 146 146 | 
             
                => "Added breakpoint 1"
         | 
| 147 147 |  | 
| 148 | 
            -
                 | 
| 148 | 
            +
                myapp:002:0> start_foo
         | 
| 149 149 | 
             
                => #<Thread:0xb7d22974 sleep>
         | 
| 150 150 |  | 
| 151 | 
            -
                 | 
| 151 | 
            +
                myapp:003:0> .bp_start
         | 
| 152 152 | 
             
                => nil
         | 
| 153 153 |  | 
| 154 | 
            -
                Breakpoint 1 in Foo#bar from  | 
| 155 | 
            -
                 | 
| 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  | 
| 159 | 
            -
                 | 
| 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  | 
| 163 | 
            -
                 | 
| 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  | 
| 167 | 
            -
                 | 
| 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  | 
| 171 | 
            -
                 | 
| 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  | 
| 175 | 
            -
                 | 
| 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  | 
| 179 | 
            -
                 | 
| 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  | 
| 183 | 
            -
                 | 
| 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  | 
| 187 | 
            -
                 | 
| 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  | 
| 192 | 
            -
                 | 
| 191 | 
            +
                Breakpoint 1 in Foo#bar from myapp.rb:10 (call)
         | 
| 192 | 
            +
                myapp:013:0> .bp_stop
         | 
| 193 193 | 
             
                => nil
         | 
| 194 194 |  | 
| 195 | 
            -
                 | 
| 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  | 
| 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 | 
            -
                 | 
| 246 | 
            +
                myapp:001:0> start_foo
         | 
| 247 247 | 
             
                => #<Thread:0xb7d11d04 sleep>
         | 
| 248 248 |  | 
| 249 | 
            -
                 | 
| 249 | 
            +
                myapp:002:0> f = rti.get_object('Foo')
         | 
| 250 250 | 
             
                => #<Foo:0xb7d116b0 @a=2>
         | 
| 251 251 |  | 
| 252 | 
            -
                 | 
| 252 | 
            +
                myapp:003:0> rti.state.block_count = 1
         | 
| 253 253 | 
             
                => 1
         | 
| 254 254 |  | 
| 255 | 
            -
                 | 
| 256 | 
            -
                 | 
| 257 | 
            -
                 | 
| 258 | 
            -
                 | 
| 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 | 
            -
                 | 
| 262 | 
            -
                 | 
| 261 | 
            +
                myapp:008:2> f.seeit
         | 
| 262 | 
            +
                myapp:009:2>
         | 
| 263 263 | 
             
                => 11
         | 
| 264 264 |  | 
| 265 | 
            -
                 | 
| 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  | 
| 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 )
         | 
    
        data/lib/rti/manager.rb
    ADDED
    
    | @@ -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
         |