hijack 0.1.9 → 0.2.0.rc1
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/bin/hijack +2 -1
- data/lib/hijack/console.rb +25 -33
- data/lib/hijack/gdb.rb +27 -31
- data/lib/hijack/helper.rb +5 -5
- data/lib/hijack/output_receiver.rb +74 -0
- data/lib/hijack/payload.rb +15 -5
- data/lib/hijack/workspace.rb +4 -5
- data/lib/hijack.rb +5 -0
- data/test/test.rb +1 -1
- metadata +17 -7
- data/bin/hijack.compiled.rbc +0 -0
- data/lib/hijack.rbc +0 -0
- data/test/test.rbc +0 -0
data/bin/hijack
CHANGED
@@ -11,9 +11,10 @@ new_argv.delete(pid)
|
|
11
11
|
|
12
12
|
new_argv.options do |opts|
|
13
13
|
opts.banner = usage
|
14
|
-
opts.on("--
|
14
|
+
opts.on("--debug", "Enable debugging.") { |v| options[:debug] = true }
|
15
15
|
opts.on("--mute", "Ignore stdout/stderr writes from the remote process.") { |v| options[:mute] = true }
|
16
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("--version", "Display this version of Hijack.") { |v| puts "Hijack #{Hijack.version}"; exit }
|
17
18
|
opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
|
18
19
|
opts.parse!
|
19
20
|
end
|
data/lib/hijack/console.rb
CHANGED
@@ -9,13 +9,15 @@ module Hijack
|
|
9
9
|
$stdout.flush
|
10
10
|
Payload.inject(@pid)
|
11
11
|
connect
|
12
|
+
check_remote_ruby_version
|
12
13
|
$stdout.write("\b" * str.size)
|
13
14
|
$stdout.flush
|
14
15
|
mirror_process
|
15
16
|
banner
|
16
17
|
execute_file
|
18
|
+
setup_at_exit
|
17
19
|
OutputReceiver.start(@remote) unless Hijack.options[:mute]
|
18
|
-
start_irb
|
20
|
+
start_irb
|
19
21
|
end
|
20
22
|
|
21
23
|
protected
|
@@ -39,33 +41,6 @@ module Hijack
|
|
39
41
|
@remote = DRbObject.new(nil, Hijack.socket_for(@pid))
|
40
42
|
end
|
41
43
|
|
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
44
|
def mirror_process
|
70
45
|
# Attempt to require all files currently loaded by the remote process so DRb can dump as many objects as possible.
|
71
46
|
#
|
@@ -101,16 +76,16 @@ module Hijack
|
|
101
76
|
end
|
102
77
|
|
103
78
|
def start_irb
|
104
|
-
ARGV.
|
79
|
+
ARGV.clear
|
80
|
+
IRB.conf[:PROMPT_MODE] = :SIMPLE
|
81
|
+
IRB.conf[:USE_READLINE] = true
|
105
82
|
IRB.setup(nil)
|
106
83
|
workspace = Hijack::Workspace.new
|
107
84
|
workspace.remote = @remote
|
108
85
|
workspace.pid = @pid
|
109
86
|
irb = IRB::Irb.new(workspace)
|
110
|
-
|
111
|
-
|
112
|
-
@CONF[:MAIN_CONTEXT] = irb.context
|
113
|
-
@CONF[:PROMPT_MODE] = :SIMPLE
|
87
|
+
IRB.conf[:IRB_RC].call irb.context if IRB.conf[:IRB_RC]
|
88
|
+
IRB.conf[:MAIN_CONTEXT] = irb.context
|
114
89
|
trap('SIGINT') { irb.signal_handle }
|
115
90
|
catch(:IRB_EXIT) { irb.eval_input }
|
116
91
|
end
|
@@ -119,6 +94,23 @@ module Hijack
|
|
119
94
|
script, ruby_version, platform, hijack_version = @remote.evaluate('[$0, RUBY_VERSION, RUBY_PLATFORM]')
|
120
95
|
puts "=> Hijacked #{@pid} (#{script}) (ruby #{ruby_version} [#{platform}])"
|
121
96
|
end
|
97
|
+
|
98
|
+
def check_remote_ruby_version
|
99
|
+
# TODO: Check patch-level.
|
100
|
+
remote_major, remote_minor = remote_ruby_version.split('.')
|
101
|
+
local_major, local_minor = RUBY_VERSION.split('.')
|
102
|
+
if remote_minor != local_minor
|
103
|
+
$stderr.puts "\nWARNING: The process you are hijacking is running Ruby #{remote_ruby_version} yet you are running #{RUBY_VERSION}."
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def remote_ruby_version
|
108
|
+
@remote_ruby_version ||= @remote.evaluate("RUBY_VERSION")
|
109
|
+
end
|
110
|
+
|
111
|
+
def setup_at_exit
|
112
|
+
at_exit { OutputReceiver.stop }
|
113
|
+
end
|
122
114
|
|
123
115
|
def execute_file
|
124
116
|
if Hijack.options[:execute]
|
data/lib/hijack/gdb.rb
CHANGED
@@ -4,28 +4,11 @@ module Hijack
|
|
4
4
|
class GDB
|
5
5
|
def initialize(pid)
|
6
6
|
@pid = pid
|
7
|
-
@verbose = Hijack.options[:
|
7
|
+
@verbose = Hijack.options[:debug]
|
8
8
|
@exec_path = File.join(Config::CONFIG['bindir'], Config::CONFIG['RUBY_INSTALL_NAME'] + Config::CONFIG['EXEEXT'])
|
9
9
|
attach
|
10
10
|
end
|
11
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
12
|
def eval(cmd)
|
30
13
|
call("(void)rb_eval_string(#{cmd.strip.gsub(/"/, '\"').inspect})")
|
31
14
|
end
|
@@ -40,40 +23,53 @@ module Hijack
|
|
40
23
|
end
|
41
24
|
|
42
25
|
protected
|
26
|
+
|
43
27
|
def attach
|
44
28
|
loop do
|
45
29
|
@gdb = IO.popen("gdb -q #{@exec_path} #{@pid} 2>&1", 'r+')
|
46
30
|
wait
|
47
|
-
if backtrace.first =~ /
|
31
|
+
if backtrace.first =~ /previous frame inner to this frame/i
|
48
32
|
detach
|
49
33
|
sleep 0.1
|
50
34
|
else
|
51
35
|
break
|
52
36
|
end
|
53
37
|
end
|
54
|
-
|
55
|
-
|
38
|
+
|
39
|
+
# TODO: Check for "Unable to attach to process-id 44528: No child processes (10)"
|
56
40
|
ensure_main_thread_not_blocked_by_join
|
57
|
-
set_trap_pending
|
58
41
|
set_breakpoint
|
59
42
|
continue
|
60
|
-
|
43
|
+
delete_breakpoint
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_breakpoint
|
47
|
+
exec("break rb_call")
|
61
48
|
end
|
62
49
|
|
63
|
-
def
|
64
|
-
|
50
|
+
def delete_breakpoint
|
51
|
+
exec("delete breakpoints")
|
65
52
|
end
|
66
53
|
|
67
|
-
def
|
68
|
-
|
54
|
+
def ensure_main_thread_not_blocked_by_join
|
55
|
+
if backtrace.any? {|line| line =~ /rb_thread_join/}
|
56
|
+
puts "\n=> Unable to hijack #{@pid} because the main thread is blocked waiting for another thread to join."
|
57
|
+
puts "=> Check that you are using the most recent version of hijack, a newer version may have solved this shortcoming."
|
58
|
+
detach
|
59
|
+
exit 1
|
60
|
+
end
|
69
61
|
end
|
70
62
|
|
71
|
-
def
|
72
|
-
|
63
|
+
def ensure_attached_to_ruby_process
|
64
|
+
unless backtrace.any? {|line| line =~ /(rb|ruby)_/}
|
65
|
+
puts "\n=> #{@pid} doesn't appear to be a Ruby process!"
|
66
|
+
detach
|
67
|
+
exit 1
|
68
|
+
end
|
73
69
|
end
|
74
70
|
|
75
|
-
def
|
76
|
-
exec(
|
71
|
+
def backtrace
|
72
|
+
@backtrace ||= exec('bt').reverse
|
77
73
|
end
|
78
74
|
|
79
75
|
def continue
|
data/lib/hijack/helper.rb
CHANGED
@@ -2,7 +2,7 @@ module Hijack
|
|
2
2
|
module Helper
|
3
3
|
class << self
|
4
4
|
def helpers
|
5
|
-
methods.find_all {|meth| meth =~ /^hijack_/}
|
5
|
+
methods.find_all {|meth| meth =~ /^hijack_/}.map { |meth| meth.to_s }
|
6
6
|
end
|
7
7
|
|
8
8
|
def find_helper(statements)
|
@@ -15,12 +15,12 @@ module Hijack
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def hijack_mute(remote)
|
18
|
-
Hijack::
|
18
|
+
Hijack::OutputReceiver.mute
|
19
19
|
true
|
20
20
|
end
|
21
21
|
|
22
22
|
def hijack_unmute(remote)
|
23
|
-
Hijack::
|
23
|
+
Hijack::OutputReceiver.unmute
|
24
24
|
true
|
25
25
|
end
|
26
26
|
|
@@ -28,11 +28,11 @@ module Hijack
|
|
28
28
|
hijack_mute(remote)
|
29
29
|
require 'rubygems'
|
30
30
|
require 'ruby-debug'
|
31
|
-
remote.evaluate(<<-
|
31
|
+
remote.evaluate(<<-RUBY)
|
32
32
|
require 'rubygems'
|
33
33
|
require 'ruby-debug'
|
34
34
|
Debugger.start_remote
|
35
|
-
|
35
|
+
RUBY
|
36
36
|
true
|
37
37
|
end
|
38
38
|
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Hijack
|
2
|
+
class OutputReceiver
|
3
|
+
attr_reader :pid
|
4
|
+
|
5
|
+
def self.start(remote)
|
6
|
+
@instance = new(remote)
|
7
|
+
@instance.start
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.stop
|
11
|
+
@instance.stop if @instance
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.mute
|
15
|
+
@instance.mute if @instance
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.unmute
|
19
|
+
@instance.unmute if @instance
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(remote)
|
23
|
+
@remote = remote
|
24
|
+
end
|
25
|
+
|
26
|
+
def start
|
27
|
+
@pid = fork do
|
28
|
+
setup_signal_traps
|
29
|
+
DRb.start_service(Hijack.socket_for(Process.pid), self)
|
30
|
+
@remote.evaluate("__hijack_output_receiver_ready_#{Process.pid}")
|
31
|
+
DRb.thread.join
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop
|
36
|
+
Process.kill("KILL", @pid)
|
37
|
+
Process.waitpid(@pid)
|
38
|
+
end
|
39
|
+
|
40
|
+
def setup_signal_traps
|
41
|
+
Signal.trap("USR1") { @mute = true }
|
42
|
+
Signal.trap("USR2") { @mute = false }
|
43
|
+
end
|
44
|
+
|
45
|
+
def mute
|
46
|
+
Process.kill("USR1", @pid)
|
47
|
+
end
|
48
|
+
|
49
|
+
def unmute
|
50
|
+
Process.kill("USR2", @pid)
|
51
|
+
end
|
52
|
+
|
53
|
+
def write(io, str)
|
54
|
+
return if @mute
|
55
|
+
get_io(io).write(str)
|
56
|
+
end
|
57
|
+
|
58
|
+
def puts(io, str)
|
59
|
+
return if @mute
|
60
|
+
get_io(io).puts(str)
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_io(io)
|
64
|
+
case io.to_s
|
65
|
+
when 'stdout'
|
66
|
+
$stdout
|
67
|
+
when 'stderr'
|
68
|
+
$stderr
|
69
|
+
else
|
70
|
+
raise "get_io cannot handle '#{io}'"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/hijack/payload.rb
CHANGED
@@ -17,7 +17,9 @@ module Hijack
|
|
17
17
|
require 'stringio'
|
18
18
|
require 'drb'
|
19
19
|
|
20
|
-
|
20
|
+
debug = #{Hijack.options[:debug] || false}
|
21
|
+
if debug || !defined?(Hijack)
|
22
|
+
puts 'Hijack: Debugging enabled' if debug
|
21
23
|
module Hijack
|
22
24
|
class OutputCopier
|
23
25
|
def self.remote
|
@@ -38,13 +40,15 @@ module Hijack
|
|
38
40
|
|
39
41
|
def self.start(pid)
|
40
42
|
@remote = DRbObject.new(nil, 'drbunix://tmp/hijack.' + pid + '.sock')
|
43
|
+
puts @remote.inspect if Hijack.debug?
|
41
44
|
|
42
45
|
class << $stdout
|
43
46
|
def write_with_copying(str)
|
44
47
|
write_without_copying(str)
|
45
48
|
begin
|
46
49
|
Hijack::OutputCopier.remote.write('stdout', str)
|
47
|
-
rescue Exception
|
50
|
+
rescue Exception => e
|
51
|
+
write_without_copying(e.message) if Hijack.debug?
|
48
52
|
Hijack.stop
|
49
53
|
end
|
50
54
|
end
|
@@ -57,7 +61,8 @@ module Hijack
|
|
57
61
|
write_without_copying(str)
|
58
62
|
begin
|
59
63
|
Hijack::OutputCopier.remote.write('stderr', str)
|
60
|
-
rescue Exception
|
64
|
+
rescue Exception => e
|
65
|
+
write_without_copying(e.message) if Hijack.debug?
|
61
66
|
Hijack.stop
|
62
67
|
end
|
63
68
|
end
|
@@ -86,8 +91,13 @@ module Hijack
|
|
86
91
|
end
|
87
92
|
end
|
88
93
|
|
89
|
-
def self.
|
94
|
+
def self.debug?
|
95
|
+
@debug
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.start(context, debug)
|
90
99
|
return if @service && @service.alive?
|
100
|
+
@debug = debug
|
91
101
|
evaluator = Hijack::Evaluator.new(context)
|
92
102
|
@service = DRb.start_service('#{Hijack.socket_for(pid)}', evaluator)
|
93
103
|
File.chmod(0600, '#{Hijack.socket_path_for(pid)}')
|
@@ -103,7 +113,7 @@ module Hijack
|
|
103
113
|
end
|
104
114
|
end
|
105
115
|
end
|
106
|
-
Hijack.start(self)
|
116
|
+
Hijack.start(self, #{Hijack.options[:debug] || false})
|
107
117
|
RUBY
|
108
118
|
end
|
109
119
|
end
|
data/lib/hijack/workspace.rb
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
module Hijack
|
2
|
-
|
2
|
+
Readline.completion_proc = Proc.new do |input|
|
3
3
|
bind = IRB.conf[:MAIN_CONTEXT].workspace.binding
|
4
4
|
if helpers = Helper.helpers_like(input)
|
5
5
|
helpers
|
6
6
|
else
|
7
7
|
IRB::InputCompletor::CompletionProc.call(input)
|
8
8
|
end
|
9
|
-
|
10
|
-
Readline.completion_proc = HijackCompletionProc
|
9
|
+
end
|
11
10
|
|
12
11
|
class Workspace < IRB::WorkSpace
|
13
12
|
attr_accessor :remote, :pid
|
14
13
|
def evaluate(context, statements, file = __FILE__, line = __LINE__)
|
15
14
|
if statements =~ /IRB\./
|
16
15
|
super
|
17
|
-
elsif statements.strip =~ /^exit/
|
16
|
+
elsif statements.strip =~ /^(exit|quit)/
|
18
17
|
remote.evaluate('__hijack_exit') rescue nil
|
19
18
|
super
|
20
19
|
elsif helper = Hijack::Helper.find_helper(statements)
|
@@ -22,7 +21,7 @@ module Hijack
|
|
22
21
|
else
|
23
22
|
begin
|
24
23
|
result = remote.evaluate(statements)
|
25
|
-
rescue DRb::DRbConnError
|
24
|
+
rescue DRb::DRbConnError => e
|
26
25
|
puts "=> Lost connection to #{@pid}!"
|
27
26
|
exit 1
|
28
27
|
end
|
data/lib/hijack.rb
CHANGED
@@ -10,6 +10,7 @@ require 'hijack/gdb'
|
|
10
10
|
require 'hijack/payload'
|
11
11
|
require 'hijack/helper'
|
12
12
|
require 'hijack/workspace'
|
13
|
+
require 'hijack/output_receiver'
|
13
14
|
|
14
15
|
module Hijack
|
15
16
|
def self.start(pid, options)
|
@@ -28,4 +29,8 @@ module Hijack
|
|
28
29
|
def self.socket_path_for(pid)
|
29
30
|
"/tmp/hijack.#{pid}.sock"
|
30
31
|
end
|
32
|
+
|
33
|
+
def self.version
|
34
|
+
"0.2.0.rc1"
|
35
|
+
end
|
31
36
|
end
|
data/test/test.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hijack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 977940574
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
- rc1
|
10
|
+
version: 0.2.0.rc1
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- Ian Leitch
|
@@ -9,12 +15,12 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date: 2009-
|
18
|
+
date: 2009-09-20 14:00:00 +10:00
|
13
19
|
default_executable:
|
14
20
|
dependencies: []
|
15
21
|
|
16
22
|
description: Provides an irb session to an existing ruby process.
|
17
|
-
email:
|
23
|
+
email: port001@gmail.com
|
18
24
|
executables:
|
19
25
|
- hijack
|
20
26
|
extensions: []
|
@@ -31,15 +37,13 @@ files:
|
|
31
37
|
- lib/hijack/helper.rb
|
32
38
|
- lib/hijack/payload.rb
|
33
39
|
- lib/hijack/workspace.rb
|
40
|
+
- lib/hijack/output_receiver.rb
|
34
41
|
- lib/hijack.rb
|
35
|
-
- lib/hijack.rbc
|
36
42
|
- test/app.rb
|
37
43
|
- test/gc.rb
|
38
44
|
- test/test.rb
|
39
|
-
- test/test.rbc
|
40
45
|
- tasks/gem.rake
|
41
46
|
- bin/hijack
|
42
|
-
- bin/hijack.compiled.rbc
|
43
47
|
- examples/rails_dispatcher.rb
|
44
48
|
has_rdoc: true
|
45
49
|
homepage: http://github.com/ileitch/hijack
|
@@ -54,18 +58,24 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
54
58
|
requirements:
|
55
59
|
- - ">="
|
56
60
|
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 0
|
57
64
|
version: "0"
|
58
65
|
version:
|
59
66
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
67
|
requirements:
|
61
68
|
- - ">="
|
62
69
|
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
63
73
|
version: "0"
|
64
74
|
version:
|
65
75
|
requirements: []
|
66
76
|
|
67
77
|
rubyforge_project:
|
68
|
-
rubygems_version: 1.3.
|
78
|
+
rubygems_version: 1.3.7
|
69
79
|
signing_key:
|
70
80
|
specification_version: 3
|
71
81
|
summary: Provides an irb session to an existing ruby process.
|
data/bin/hijack.compiled.rbc
DELETED
Binary file
|
data/lib/hijack.rbc
DELETED
Binary file
|
data/test/test.rbc
DELETED
Binary file
|