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