hijack 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2009 Ian Leitch
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,121 @@
1
+ = Hijack: Provides an irb session to an existing ruby process.
2
+
3
+ == Intro
4
+
5
+ Hijack allows you to connect to any ruby process and execute code as if it were a normal irb session. Hijack does not require your target process to require any hijacking code, hijack is able to connect to any ruby process. It achieves this by using gdb to inject a payload into the process which starts up a DRb server, hijack then detaches gdb and reconnects via DRb. Please note that gdb will halt your target process while it is attached, though the injection process is very quick and your process should only be halted for a few milliseconds.
6
+
7
+ Hijack uses DRb over a unix socket file, so you need to be on the same machine as the process you want to hijack. This is by design for security reasons. You also need to run the hijack client as the same user as the remote process.
8
+
9
+ == Using Hijack
10
+
11
+ $ hijack 16451
12
+ => Hijacked 16451 (my_script.rb) (ruby 1.8.7 [i686-darwin9])
13
+ >>
14
+
15
+ == Using ruby-debug
16
+
17
+ Hijack can be used to start ruby-debug in your target process, for example:
18
+
19
+ $ hijack 61378
20
+ => Hijacked 61378 (/opt/local/bin/thin) (ruby 1.8.7 [i686-darwin9])
21
+ >> hijack_debug_mode
22
+ => true
23
+
24
+ We've enabled debug mode, but we still need to insert a breakpoint:
25
+
26
+ >> ActionController::Dispatcher.class_eval do
27
+ >> class << self
28
+ >> def dispatch_with_debugger(cgi, session_options, output)
29
+ >> debugger
30
+ >> dispatch_without_debugger(cgi, session_options, output)
31
+ >> end
32
+ >> alias_method :dispatch_without_debugger, :dispatch
33
+ >> alias_method :dispatch, :dispatch_with_debugger
34
+ >> end
35
+ >> end
36
+
37
+ Now tell hijack that we can start debugging:
38
+
39
+ >> hijack_debug_start
40
+ Connected.
41
+
42
+ Point your browser at http://0.0.0.0:3000 to trigger the breakpoint and ruby-debug's console will appear:
43
+
44
+ >> hijack_debug_start
45
+ Connected.
46
+ (eval):5
47
+ (rdb:4) step
48
+ /Users/ian/Projects/zioko/vendor/rails/actionpack/lib/action_controller/dispatcher.rb:28 new(output).dispatch_cgi(cgi, session_options)
49
+ (rdb:4) backtrace
50
+ --> #0 /Users/ian/Projects/zioko/vendor/rails/actionpack/lib/action_controller/dispatcher.rb:28 in 'dispatch_without_debugger'
51
+ #1 (eval):5 in 'dispatch'
52
+ #2 /opt/local/lib/ruby/gems/1.8/gems/thin-1.2.2/lib/rack/adapter/rails.rb:81 in 'call'
53
+ #3 /opt/local/lib/ruby/gems/1.8/gems/thin-1.2.2/lib/rack/adapter/rails.rb:69 in 'call'
54
+ #4 /opt/local/lib/ruby/gems/1.8/gems/thin-1.2.2/lib/thin/connection.rb:76 in 'pre_process'
55
+ #5 /opt/local/lib/ruby/gems/1.8/gems/thin-1.2.2/lib/thin/connection.rb:74 in 'pre_process'
56
+ #6 /opt/local/lib/ruby/gems/1.8/gems/thin-1.2.2/lib/thin/connection.rb:57 in 'process'
57
+ #7 /opt/local/lib/ruby/gems/1.8/gems/thin-1.2.2/lib/thin/connection.rb:42 in 'receive_data'
58
+ (rdb:4)
59
+
60
+ One caveat is that ruby-debug's 'irb' command will not work because the debug connection is remote, you also can't use hijack's irb console whilst debugging. A future version of hijack will hopefully allow you to switch between the two.
61
+
62
+ == Monkey Patching
63
+
64
+ Just as in a normal irb session, you can redefine the code running in your target process.
65
+
66
+ This example redefines ActionController's dispatcher to print basic request activity (the example can be found in examples/rails_dispatcher.rb).
67
+
68
+ Start up a Thin web server:
69
+
70
+ $ thin start
71
+ >> Using rails adapter
72
+ >> Thin web server (v1.2.2 codename I Find Your Lack of Sauce Disturbing)
73
+ >> Maximum connections set to 1024
74
+ >> Listening on 0.0.0.0:3000, CTRL+C to stop
75
+
76
+ In another console hijack the Thin process:
77
+
78
+ $ ps | grep thin
79
+ 61160 ttys001 0:01.43 /opt/local/bin/ruby /opt/local/bin/thin start
80
+
81
+ $ hijack 61160
82
+ => Hijacked 61160 (/opt/local/bin/thin) (ruby 1.8.7 [i686-darwin9])
83
+ >> ActionController::Dispatcher.class_eval do
84
+ ?> class << self
85
+ >> def dispatch_with_spying(cgi, session_options, output)
86
+ >> env = cgi.__send__(:env_table)
87
+ >> puts "#{Time.now.strftime('%Y/%m/%d %H:%M:%S')} - #{env['REMOTE_ADDR']} - #{env['REQUEST_URI']}"
88
+ >> dispatch_without_spying(cgi, session_options, output)
89
+ >> end
90
+ >> alias_method :dispatch_without_spying, :dispatch
91
+ >> alias_method :dispatch, :dispatch_with_spying
92
+ >> end
93
+ >> end
94
+
95
+ Point your browser to http://0.0.0.0:3000.
96
+
97
+ Back in hijack you'll see your browsing activity:
98
+
99
+ 2009/08/22 14:24:48 - 127.0.0.1 - /
100
+ 2009/08/22 14:24:53 - 127.0.0.1 - /login
101
+ 2009/08/22 14:24:54 - 127.0.0.1 - /signup
102
+
103
+ Instead of pasting your code into hijack, you can pass hijack the -e option execute a local file on the target process.
104
+
105
+ == Process Output
106
+
107
+ By default hijack will forward your process output to the hijack client. This can get a little messy if your trying to write code at the same time as the target process writes to STDOUT/STDERR. You can mute and unmute the process with:
108
+
109
+ >> hijack_mute
110
+ => true
111
+ >> hijack_unmute
112
+ => true
113
+
114
+ For ease of use, hijack helper methods are discoverable with tab completion. hi<tab><tab> will give you a list of available helpers.
115
+
116
+ == Process Mirroring
117
+
118
+ DRb cannot dump objects to the hijack client for types that are not loaded in the client process. E.g if the remote process had required ActiveRecord and you tried to dump ActiveRecord::Base back to the client, DRb would instead return a DRb::Unknown object as ActiveRecord
119
+ isn't loaded in the hijack client.
120
+
121
+ To work around this, when hijack connects to a remote process it will inspect all the files required by the process and also attempt to require them itself. This may not work for all object types however so you may still get a warning when an object cannot be dumped.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ * Improve startup experience
2
+ * Add a hijack_exec helper to execute a local .rb on the target
data/bin/hijack ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require File.dirname(__FILE__) + '/../lib/hijack'
4
+ require 'hijack'
5
+
6
+ options = {}
7
+ usage = 'Usage: hijack [options] <PID>'
8
+ pid = ARGV.find {|o| o =~ /\d/}
9
+ new_argv = ARGV.clone
10
+ new_argv.delete(pid)
11
+
12
+ new_argv.options do |opts|
13
+ opts.banner = usage
14
+ opts.on("--gdb-debug", "Print gdb activity to the console.") { |v| options[:gdb_debug] = true }
15
+ opts.on("--mute", "Ignore stdout/stderr writes from the remote process.") { |v| options[:mute] = true }
16
+ opts.on("-e", "--execute=FILE", String, "Execute the specified file in the remote process and then disconnect.") { |v| options[:execute] = v }
17
+ opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
18
+ opts.parse!
19
+ end
20
+
21
+ if pid.nil? || pid.to_s == ''
22
+ puts usage
23
+ exit 1
24
+ end
25
+
26
+ Hijack.start(pid, options)
Binary file
@@ -0,0 +1,11 @@
1
+ ActionController::Dispatcher.class_eval do
2
+ class << self
3
+ def dispatch_with_spying(cgi, session_options, output)
4
+ env = cgi.__send__(:env_table)
5
+ puts "#{Time.now.strftime('%Y/%m/%d %H:%M:%S')} - #{env['REMOTE_ADDR']} - #{env['REQUEST_URI']}"
6
+ dispatch_without_spying(cgi, session_options, output)
7
+ end
8
+ alias_method :dispatch_without_spying, :dispatch
9
+ alias_method :dispatch, :dispatch_with_spying
10
+ end
11
+ end
@@ -0,0 +1,137 @@
1
+ module Hijack
2
+ class Console
3
+ def initialize(pid)
4
+ @pid = pid
5
+ @remote = nil
6
+ check_pid
7
+ str = "=> Hijacking..."
8
+ $stdout.write(str)
9
+ $stdout.flush
10
+ Payload.inject(@pid)
11
+ connect
12
+ $stdout.write("\b" * str.size)
13
+ $stdout.flush
14
+ mirror_process
15
+ banner
16
+ execute_file
17
+ OutputReceiver.start(@remote) unless Hijack.options[:mute]
18
+ start_irb
19
+ end
20
+
21
+ protected
22
+ def check_pid
23
+ begin
24
+ Process.kill(0, @pid.to_i)
25
+ rescue Errno::EPERM
26
+ puts "=> You do not have the correct permissions to hijack #{@pid}"
27
+ exit 1
28
+ rescue Errno::ESRCH
29
+ puts "=> No such process #{@pid}"
30
+ exit 1
31
+ end
32
+ end
33
+
34
+ def connect
35
+ loop do
36
+ break if File.exists?(Hijack.socket_path_for(@pid))
37
+ sleep 0.01
38
+ end
39
+ @remote = DRbObject.new(nil, Hijack.socket_for(@pid))
40
+ end
41
+
42
+ module OutputReceiver
43
+ class << self
44
+ def mute
45
+ @mute = true
46
+ end
47
+
48
+ def unmute(remote)
49
+ start(remote) unless @started
50
+ @mute = false
51
+ end
52
+
53
+ def write(where, str)
54
+ Object.const_get(where.upcase).write(str) unless @mute
55
+ end
56
+
57
+ def puts(where, str)
58
+ Object.const_get(where.upcase).puts(str) unless @mute
59
+ end
60
+
61
+ def start(remote)
62
+ DRb.start_service(Hijack.socket_for(Process.pid), self)
63
+ remote.evaluate("__hijack_output_receiver_ready_#{Process.pid}")
64
+ @started = true
65
+ end
66
+ end
67
+ end
68
+
69
+ def mirror_process
70
+ # Attempt to require all files currently loaded by the remote process so DRb can dump as many objects as possible.
71
+ #
72
+ # We have to first require everything in reverse order and then in the original order.
73
+ # This is because when you require file_a.rb which first sets a constant then requires file_b.rb
74
+ # the $" array will contain file_b.rb before file_a.rb. But if we require file_b.rb before file_a.rb
75
+ # we'll get a missing constant error.
76
+ load_path, loaded_files = @remote.evaluate('[$:, $"]')
77
+ to_load = (loaded_files - $").uniq
78
+ completion_percentage = 0
79
+ str = '=> Mirroring: '
80
+ percent_str = ''
81
+ $stdout.write(str)
82
+ $stdout.flush
83
+ $:.clear
84
+ $:.push(*load_path)
85
+ orig_stderr = $stderr
86
+ $stderr = File.open('/dev/null')
87
+ (to_load.reverse + to_load).each_with_index do |file, i|
88
+ begin
89
+ require file
90
+ rescue Exception, LoadError
91
+ end
92
+ $stdout.write("\b" * percent_str.size)
93
+ $stdout.flush
94
+ percent_str = "#{(((i + 1) / (to_load.size * 2).to_f) * 100.0).round}%"
95
+ $stdout.write(percent_str)
96
+ $stdout.flush
97
+ end
98
+ $stderr = orig_stderr
99
+ $stdout.write("\b" * (str.size + percent_str.size))
100
+ $stdout.flush
101
+ end
102
+
103
+ def start_irb
104
+ ARGV.replace ["--simple-prompt"]
105
+ IRB.setup(nil)
106
+ workspace = Hijack::Workspace.new
107
+ workspace.remote = @remote
108
+ workspace.pid = @pid
109
+ irb = IRB::Irb.new(workspace)
110
+ @CONF = IRB.instance_variable_get(:@CONF)
111
+ @CONF[:IRB_RC].call irb.context if @CONF[:IRB_RC]
112
+ @CONF[:MAIN_CONTEXT] = irb.context
113
+ @CONF[:PROMPT_MODE] = :SIMPLE
114
+ trap('SIGINT') { irb.signal_handle }
115
+ catch(:IRB_EXIT) { irb.eval_input }
116
+ end
117
+
118
+ def banner
119
+ script, ruby_version, platform, hijack_version = @remote.evaluate('[$0, RUBY_VERSION, RUBY_PLATFORM]')
120
+ puts "=> Hijacked #{@pid} (#{script}) (ruby #{ruby_version} [#{platform}])"
121
+ end
122
+
123
+ def execute_file
124
+ if Hijack.options[:execute]
125
+ if File.exists?(Hijack.options[:execute])
126
+ $stdout.write("=> Executing #{Hijack.options[:execute]}... ")
127
+ $stdout.flush
128
+ @remote.evaluate(File.read(Hijack.options[:execute]))
129
+ puts "done!"
130
+ exit
131
+ else
132
+ puts "=> Can't find #{Hijack.options[:execute]} to execute!"
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
data/lib/hijack/gdb.rb ADDED
@@ -0,0 +1,111 @@
1
+ # Based on gdb.rb by Jamis Buck, thanks Jamis!
2
+
3
+ module Hijack
4
+ class GDB
5
+ def initialize(pid)
6
+ @pid = pid
7
+ @verbose = Hijack.options[:gdb_debug]
8
+ @exec_path = File.join(Config::CONFIG['bindir'], Config::CONFIG['RUBY_INSTALL_NAME'] + Config::CONFIG['EXEEXT'])
9
+ attach
10
+ end
11
+
12
+ def ensure_attached_to_ruby_process
13
+ unless backtrace.any? {|line| line =~ /rb_/}
14
+ puts "\n=> #{@pid} doesn't appear to be a Ruby process!"
15
+ detach
16
+ exit 1
17
+ end
18
+ end
19
+
20
+ def ensure_main_thread_not_blocked_by_join
21
+ if backtrace.any? {|line| line =~ /rb_thread_join/}
22
+ puts "\n=> Unable to hijack #{@pid} because the main thread is blocked waiting for another thread to join."
23
+ puts "=> Check that you are using the most recent version of hijack, a newer version may have solved this shortcoming."
24
+ detach
25
+ exit 1
26
+ end
27
+ end
28
+
29
+ def eval(cmd)
30
+ call("(void)rb_eval_string(#{cmd.strip.gsub(/"/, '\"').inspect})")
31
+ end
32
+
33
+ def detach
34
+ return unless @gdb
35
+ exec('detach')
36
+ exec('quit')
37
+ @backtrace = nil
38
+ @gdb.close
39
+ @gdb = nil
40
+ end
41
+
42
+ protected
43
+ def attach
44
+ loop do
45
+ @gdb = IO.popen("gdb -q #{@exec_path} #{@pid} 2>&1", 'r+')
46
+ wait
47
+ if backtrace.first =~ /Previous frame inner to this frame/
48
+ detach
49
+ sleep 0.1
50
+ else
51
+ break
52
+ end
53
+ end
54
+
55
+ ensure_attached_to_ruby_process
56
+ ensure_main_thread_not_blocked_by_join
57
+ set_trap_pending
58
+ set_breakpoint
59
+ continue
60
+ clear_breakpoint
61
+ end
62
+
63
+ def backtrace
64
+ @backtrace ||= exec('bt').reverse
65
+ end
66
+
67
+ def set_trap_pending
68
+ exec("set variable (int)rb_trap_pending=1")
69
+ end
70
+
71
+ def set_breakpoint
72
+ exec("break rb_trap_exec")
73
+ end
74
+
75
+ def clear_breakpoint
76
+ exec("clear rb_trap_exec")
77
+ end
78
+
79
+ def continue
80
+ exec('continue')
81
+ end
82
+
83
+ def call(cmd)
84
+ exec("call #{cmd}")
85
+ end
86
+
87
+ def exec(str)
88
+ puts("(gdb) #{str}") if @verbose
89
+ @gdb.puts(str)
90
+ wait
91
+ end
92
+
93
+ def wait
94
+ lines = []
95
+ line = ''
96
+ while result = IO.select([@gdb])
97
+ next if result.empty?
98
+ c = @gdb.read(1)
99
+ break if c.nil?
100
+ line << c
101
+ break if line == "(gdb) " || line == " >"
102
+ if line[-1] == ?\n
103
+ lines << line
104
+ line = ""
105
+ end
106
+ end
107
+ puts lines.map { |l| "> #{l}" } if @verbose
108
+ lines
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,45 @@
1
+ module Hijack
2
+ module Helper
3
+ class << self
4
+ def helpers
5
+ methods.find_all {|meth| meth =~ /^hijack_/}
6
+ end
7
+
8
+ def find_helper(statements)
9
+ helpers.include?(statements.strip) ? statements.strip : nil
10
+ end
11
+
12
+ def helpers_like(str)
13
+ found = helpers.find_all { |helper| helper =~ Regexp.new(str) }
14
+ found.empty? ? nil : found
15
+ end
16
+
17
+ def hijack_mute(remote)
18
+ Hijack::Console::OutputReceiver.mute
19
+ true
20
+ end
21
+
22
+ def hijack_unmute(remote)
23
+ Hijack::Console::OutputReceiver.unmute(remote)
24
+ true
25
+ end
26
+
27
+ def hijack_debug_mode(remote)
28
+ hijack_mute(remote)
29
+ require 'rubygems'
30
+ require 'ruby-debug'
31
+ remote.evaluate(<<-RB)
32
+ require 'rubygems'
33
+ require 'ruby-debug'
34
+ Debugger.start_remote
35
+ RB
36
+ true
37
+ end
38
+
39
+ def hijack_debug_start(remote)
40
+ Debugger.start_client
41
+ true
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,110 @@
1
+ module Hijack
2
+ class Payload
3
+ def self.inject(pid)
4
+ gdb = nil
5
+ trap('SIGINT') do
6
+ puts
7
+ @received_sigint = true
8
+ end
9
+ gdb = GDB.new(pid)
10
+ gdb.eval(payload(pid))
11
+ gdb.detach
12
+ exit if @received_sigint
13
+ end
14
+
15
+ def self.payload(pid)
16
+ <<-RUBY
17
+ require 'stringio'
18
+ require 'drb'
19
+
20
+ unless defined?(Hijack)
21
+ module Hijack
22
+ class OutputCopier
23
+ def self.remote
24
+ @remote
25
+ end
26
+
27
+ def self.stop
28
+ @remote = nil
29
+ [$stdout, $stderr].each do |io|
30
+ if io.respond_to?(:write_with_copying)
31
+ class << io
32
+ alias_method :write, :write_without_copying
33
+ remove_method :write_with_copying
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def self.start(pid)
40
+ @remote = DRbObject.new(nil, 'drbunix://tmp/hijack.' + pid + '.sock')
41
+
42
+ class << $stdout
43
+ def write_with_copying(str)
44
+ write_without_copying(str)
45
+ begin
46
+ Hijack::OutputCopier.remote.write('stdout', str)
47
+ rescue Exception
48
+ Hijack.stop
49
+ end
50
+ end
51
+ alias_method :write_without_copying, :write
52
+ alias_method :write, :write_with_copying
53
+ end
54
+
55
+ class << $stderr
56
+ def write_with_copying(str)
57
+ write_without_copying(str)
58
+ begin
59
+ Hijack::OutputCopier.remote.write('stderr', str)
60
+ rescue Exception
61
+ Hijack.stop
62
+ end
63
+ end
64
+ alias_method :write_without_copying, :write
65
+ alias_method :write, :write_with_copying
66
+ end
67
+ end
68
+ end
69
+
70
+ class Evaluator
71
+ def initialize(context)
72
+ @context = context
73
+ @file = __FILE__
74
+ end
75
+
76
+ def evaluate(rb)
77
+ if rb =~ /__hijack_output_receiver_ready_([\\d]+)/
78
+ OutputCopier.start($1)
79
+ elsif rb =~ /__hijack_get_remote_file_name/
80
+ @file
81
+ elsif rb =~ /__hijack_exit/
82
+ Hijack.stop
83
+ else
84
+ @context.instance_eval(rb)
85
+ end
86
+ end
87
+ end
88
+
89
+ def self.start(context)
90
+ return if @service && @service.alive?
91
+ evaluator = Hijack::Evaluator.new(context)
92
+ @service = DRb.start_service('#{Hijack.socket_for(pid)}', evaluator)
93
+ File.chmod(0600, '#{Hijack.socket_path_for(pid)}')
94
+ end
95
+
96
+ def self.stop
97
+ begin
98
+ OutputCopier.stop
99
+ @service.stop_service
100
+ @service = nil
101
+ rescue Exception
102
+ end
103
+ end
104
+ end
105
+ end
106
+ Hijack.start(self)
107
+ RUBY
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,37 @@
1
+ module Hijack
2
+ HijackCompletionProc = proc {|input|
3
+ bind = IRB.conf[:MAIN_CONTEXT].workspace.binding
4
+ if helpers = Helper.helpers_like(input)
5
+ helpers
6
+ else
7
+ IRB::InputCompletor::CompletionProc.call(input)
8
+ end
9
+ }
10
+ Readline.completion_proc = HijackCompletionProc
11
+
12
+ class Workspace < IRB::WorkSpace
13
+ attr_accessor :remote, :pid
14
+ def evaluate(context, statements, file = __FILE__, line = __LINE__)
15
+ if statements =~ /IRB\./
16
+ super
17
+ elsif statements.strip =~ /^exit/
18
+ remote.evaluate('__hijack_exit') rescue nil
19
+ super
20
+ elsif helper = Hijack::Helper.find_helper(statements)
21
+ Hijack::Helper.send(helper, remote)
22
+ else
23
+ begin
24
+ result = remote.evaluate(statements)
25
+ rescue DRb::DRbConnError
26
+ puts "=> Lost connection to #{@pid}!"
27
+ exit 1
28
+ end
29
+ if result.kind_of?(DRb::DRbUnknown)
30
+ puts "=> Hijack: Unable to dump unknown object type '#{result.name}', try inspecting it instead."
31
+ return nil
32
+ end
33
+ result
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/hijack.rb ADDED
@@ -0,0 +1,31 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ require 'stringio'
3
+ require 'drb'
4
+ require 'drb/unix'
5
+ require 'rbconfig'
6
+ require 'irb'
7
+ require 'irb/completion'
8
+ require 'hijack/console'
9
+ require 'hijack/gdb'
10
+ require 'hijack/payload'
11
+ require 'hijack/helper'
12
+ require 'hijack/workspace'
13
+
14
+ module Hijack
15
+ def self.start(pid, options)
16
+ @@options = options
17
+ Console.new(pid)
18
+ end
19
+
20
+ def self.options
21
+ @@options
22
+ end
23
+
24
+ def self.socket_for(pid)
25
+ "drbunix:/#{socket_path_for(pid)}"
26
+ end
27
+
28
+ def self.socket_path_for(pid)
29
+ "/tmp/hijack.#{pid}.sock"
30
+ end
31
+ end
data/lib/hijack.rbc ADDED
Binary file
data/tasks/gem.rake ADDED
@@ -0,0 +1,41 @@
1
+ require 'rake/gempackagetask'
2
+ require 'yaml'
3
+
4
+ HIJACK_VERSION = '0.1.9'
5
+
6
+ task :clean => :clobber_package
7
+
8
+ spec = Gem::Specification.new do |s|
9
+ s.name = 'hijack'
10
+ s.version = HIJACK_VERSION
11
+ s.platform = Gem::Platform::RUBY
12
+ s.summary =
13
+ s.description = 'Provides an irb session to an existing ruby process.'
14
+ s.author = "Ian Leitch"
15
+ s.email = 'ian.leitch@systino.net'
16
+ s.homepage = 'http://github.com/ileitch/hijack'
17
+ s.has_rdoc = false
18
+ s.files = %w(COPYING TODO README.rdoc Rakefile) + Dir.glob("{lib,test,tasks,bin,examples}/**/*")
19
+ s.bindir = "bin"
20
+ s.executables = %w( hijack )
21
+ s.require_path = "lib"
22
+ end
23
+
24
+ Rake::GemPackageTask.new(spec) do |p|
25
+ p.gem_spec = spec
26
+ end
27
+
28
+ namespace :gem do
29
+ desc "Update the gemspec for GitHub's gem server"
30
+ task :github do
31
+ File.open("hijack.gemspec", 'w') { |f| f << YAML.dump(spec) }
32
+ end
33
+ end
34
+
35
+ task :install => [:clean, :clobber, :package] do
36
+ sh "sudo gem install pkg/#{spec.full_name}.gem"
37
+ end
38
+
39
+ task :uninstall => :clean do
40
+ sh "sudo gem uninstall -v #{HIJACK_VERSION} -x hijack"
41
+ end
data/test/app.rb ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/ruby
2
+ require 'rubygems'
3
+ require 'sinatra'
4
+
5
+ get '/' do
6
+ 'You got it'
7
+ end
data/test/gc.rb ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/ruby
2
+
3
+ class MyObject
4
+ def initialize(i)
5
+ @i = i
6
+ end
7
+ end
8
+
9
+ puts Process.pid
10
+ n = 0
11
+ loop do
12
+ (n+=1).times {|i| MyObject.new(i)}
13
+ GC.start
14
+ end
data/test/test.rb ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/ruby
2
+
3
+ class SomeClass
4
+ end
5
+
6
+ def do_shit(i)
7
+ puts i
8
+ end
9
+
10
+ p Process.pid
11
+
12
+ t = Thread.new do
13
+ i = 0
14
+ loop do
15
+ sleep 2
16
+ do_shit(i+=1)
17
+ end
18
+ end
19
+
20
+ t.join
data/test/test.rbc ADDED
Binary file
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hijack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.9
5
+ platform: ruby
6
+ authors:
7
+ - Ian Leitch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-23 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Provides an irb session to an existing ruby process.
17
+ email: ian.leitch@systino.net
18
+ executables:
19
+ - hijack
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - COPYING
26
+ - TODO
27
+ - README.rdoc
28
+ - Rakefile
29
+ - lib/hijack/console.rb
30
+ - lib/hijack/gdb.rb
31
+ - lib/hijack/helper.rb
32
+ - lib/hijack/payload.rb
33
+ - lib/hijack/workspace.rb
34
+ - lib/hijack.rb
35
+ - lib/hijack.rbc
36
+ - test/app.rb
37
+ - test/gc.rb
38
+ - test/test.rb
39
+ - test/test.rbc
40
+ - tasks/gem.rake
41
+ - bin/hijack
42
+ - bin/hijack.compiled.rbc
43
+ - examples/rails_dispatcher.rb
44
+ has_rdoc: true
45
+ homepage: http://github.com/ileitch/hijack
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options: []
50
+
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.3.5
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Provides an irb session to an existing ruby process.
72
+ test_files: []
73
+