Parent

Class/Module Index [+]

Quicksearch

CrashWatch::GdbController

Attributes

debug[RW]

Public Class Methods

new() click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 55
def initialize
  @pid, @in, @out = popen_command(find_gdb, "-n", "-q")
  execute("set prompt ")
end

Public Instance Methods

all_threads_backtraces() click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 145
def all_threads_backtraces
  execute("thread apply all bt full").strip
end
attach(pid) click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 119
def attach(pid)
  pid = pid.to_s.strip
  raise ArgumentError if pid.empty?
  result = execute("attach #{pid}")
  result !~ /(No such process|Unable to access task|Operation not permitted)/
end
call(code) click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 126
def call(code)
  result = execute("call #{code}")
  result =~ /= (.*)$/
  $1
end
close() click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 93
def close
  if !closed?
    begin
      execute("detach", 5)
      execute("quit", 5) if !closed?
    rescue Errno::EPIPE
    end
    if !closed?
      @in.close
      @out.close
      Process.waitpid(@pid)
      @pid = nil
    end
  end
end
close!() click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 109
def close!
  if !closed?
    @in.close
    @out.close
    Process.kill('KILL', @pid)
    Process.waitpid(@pid)
    @pid = nil
  end
end
closed?() click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 89
def closed?
  !@pid
end
current_thread() click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 136
def current_thread
  execute("thread") =~ /Current thread is (.+?) /
  $1
end
current_thread_backtrace() click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 141
def current_thread_backtrace
  execute("bt full").strip
end
execute(command_string, timeout = nil) click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 60
def execute(command_string, timeout = nil)
  raise "GDB session is already closed" if !@pid
  puts "gdb write #{command_string.inspect}" if @debug
  @in.puts(command_string)
  @in.puts("echo \\n#{END_OF_RESPONSE_MARKER}\\n")
  done = false
  result = ""
  while !done
    begin
      if select([@out], nil, nil, timeout)
        line = @out.readline
        puts "gdb read #{line.inspect}" if @debug
        if line == "#{END_OF_RESPONSE_MARKER}\n"
          done = true
        else
          result << line
        end
      else
        close!
        done = true
        result = nil
      end
    rescue EOFError
      done = true
    end
  end
  result
end
program_counter() click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 132
def program_counter
  execute("p/x $pc").gsub(/.* = /, '')
end
ruby_backtrace() click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 149
def ruby_backtrace
  filename = "/tmp/gdb-capture.#{@pid}.txt"
  
  orig_stdout_fd_copy = call("(int) dup(1)")
  new_stdout = call(%{(void *) fopen("#{filename}", "w")})
  new_stdout_fd = call("(int) fileno(#{new_stdout})")
  call("(int) dup2(#{new_stdout_fd}, 1)")
  
  # Let's hope stdout is set to line buffered or unbuffered mode...
  call("(void) rb_backtrace()")
  
  call("(int) dup2(#{orig_stdout_fd_copy}, 1)")
  call("(int) fclose(#{new_stdout})")
  call("(int) close(#{orig_stdout_fd_copy})")
  
  if File.exist?(filename)
    result = File.read(filename)
    result.strip!
    if result.empty?
      nil
    else
      result
    end
  else
    nil
  end
ensure
  if filename
    File.unlink(filename) rescue nil
  end
end
wait_until_exit() click to toggle source
# File lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 181
def wait_until_exit
  execute("break _exit")
  
  signal = nil
  backtraces = nil
  snapshot = nil
  
  while true
    result = execute("continue")
    if result =~ /^Program received signal (.+?),/
      signal = $1
      backtraces = execute("thread apply all bt full").strip
      if backtraces.empty?
        backtraces = execute("bt full").strip
      end
      snapshot = yield(self) if block_given?
      
      # This signal may or may not be immediately fatal; the
      # signal might be ignored by the process, or the process
      # has some clever signal handler that fixes the state,
      # or maybe the signal handler must run some cleanup code
      # before killing the process. Let's find out by running
      # the next machine instruction.
      old_program_counter = program_counter
      result = execute("stepi")
      if result =~ /^Program received signal .+?,/
        # Yes, it was fatal. Here we don't care whether the
        # instruction caused a different signal. The last
        # one is probably what we're interested in.
        return ExitInfo.new(nil, signal, backtraces, snapshot)
      elsif result =~ /^Program (terminated|exited)/ || result =~ /^Breakpoint .*? _exit/
        # Running the next instruction causes the program to terminate.
        # Not sure what's going on but the previous signal and
        # backtrace is probably what we're interested in.
        return ExitInfo.new(nil, signal, backtraces, snapshot)
      elsif old_program_counter == program_counter
        # The process cannot continue but we're not sure what GDB
        # is telling us.
        raise "Unexpected GDB output: #{result}"
      end
      # else:
      # The signal doesn't isn't immediately fatal, so save current
      # status, continue, and check whether the process exits later.
    elsif result =~ /^Program terminated with signal (.+?),/
      if $1 == signal
        # Looks like the signal we trapped earlier
        # caused an exit.
        return ExitInfo.new(nil, signal, backtraces, snapshot)
      else
        return ExitInfo.new(nil, signal, nil, snapshot)
      end
    elsif result =~ /^Breakpoint .*? _exit /
      backtraces = execute("thread apply all bt full").strip
      if backtraces.empty?
        backtraces = execute("bt full").strip
      end
      snapshot = yield(self) if block_given?
      # On OS X, gdb may fail to return from the 'continue' command
      # even though the process exited. Kernel bug? In any case,
      # we put a timeout here so that we don't wait indefinitely.
      result = execute("continue", 10)
      if result =~ /^Program exited with code (\d+)\.$/
        return ExitInfo.new($1.to_i, nil, backtraces, snapshot)
      elsif result =~ /^Program exited normally/
        return ExitInfo.new(0, nil, backtraces, snapshot)
      else
        return ExitInfo.new(nil, nil, backtraces, snapshot)
      end
    elsif result =~ /^Program exited with code (\d+)\.$/
      return ExitInfo.new($1.to_i, nil, nil, nil)
    elsif result =~ /^Program exited normally/
      return ExitInfo.new(0, nil, nil, nil)
    else
      return ExitInfo.new(nil, nil, nil, nil)
    end
  end
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.