hijack 0.1.9 → 0.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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("--gdb-debug", "Print gdb activity to the console.") { |v| options[:gdb_debug] = true }
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
@@ -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.replace ["--simple-prompt"]
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
- @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
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[:gdb_debug]
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 =~ /Previous frame inner to this frame/
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
- ensure_attached_to_ruby_process
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
- clear_breakpoint
43
+ delete_breakpoint
44
+ end
45
+
46
+ def set_breakpoint
47
+ exec("break rb_call")
61
48
  end
62
49
 
63
- def backtrace
64
- @backtrace ||= exec('bt').reverse
50
+ def delete_breakpoint
51
+ exec("delete breakpoints")
65
52
  end
66
53
 
67
- def set_trap_pending
68
- exec("set variable (int)rb_trap_pending=1")
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 set_breakpoint
72
- exec("break rb_trap_exec")
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 clear_breakpoint
76
- exec("clear rb_trap_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::Console::OutputReceiver.mute
18
+ Hijack::OutputReceiver.mute
19
19
  true
20
20
  end
21
21
 
22
22
  def hijack_unmute(remote)
23
- Hijack::Console::OutputReceiver.unmute(remote)
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(<<-RB)
31
+ remote.evaluate(<<-RUBY)
32
32
  require 'rubygems'
33
33
  require 'ruby-debug'
34
34
  Debugger.start_remote
35
- RB
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
@@ -17,7 +17,9 @@ module Hijack
17
17
  require 'stringio'
18
18
  require 'drb'
19
19
 
20
- unless defined?(Hijack)
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.start(context)
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
@@ -1,20 +1,19 @@
1
1
  module Hijack
2
- HijackCompletionProc = proc {|input|
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
@@ -12,7 +12,7 @@ p Process.pid
12
12
  t = Thread.new do
13
13
  i = 0
14
14
  loop do
15
- sleep 2
15
+ sleep 1
16
16
  do_shit(i+=1)
17
17
  end
18
18
  end
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hijack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
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-10-23 00:00:00 -04:00
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: ian.leitch@systino.net
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.5
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.
Binary file
data/lib/hijack.rbc DELETED
Binary file
data/test/test.rbc DELETED
Binary file