# File src/ruby_supportlib/phusion_passenger/vendor/crash_watch/gdb_controller.rb, line 182
    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