content: /*
   * Copyright (C) 2013 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.am;
  
  import android.app.ApplicationErrorReport.CrashInfo;
  import android.system.ErrnoException;
  import android.system.Os;
  import android.system.StructTimeval;
  import android.system.UnixSocketAddress;
  import android.util.Slog;
  
  import static android.system.OsConstants.*;
  
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.FileDescriptor;
  import java.io.InterruptedIOException;
  
  /**
   * Set up a Unix domain socket that debuggerd will connect() to in
   * order to write a description of a native crash.  The crash info is
   * then parsed and forwarded to the ActivityManagerService's normal
   * crash handling code.
   *
   * Note that this component runs in a separate thread.
   */
  final class NativeCrashListener extends Thread {
      static final String TAG = "NativeCrashListener";
      static final boolean DEBUG = false;
      static final boolean MORE_DEBUG = DEBUG && false;
  
      // Must match the path defined in debuggerd.c.
      static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket";
  
      // Use a short timeout on socket operations and abandon the connection
      // on hard errors, just in case debuggerd goes out to lunch.
      static final long SOCKET_TIMEOUT_MILLIS = 10000;  // 10 seconds
  
      final ActivityManagerService mAm;
  
      /*
       * Spin the actual work of handling a debuggerd crash report into a
       * separate thread so that the listener can go immediately back to
       * accepting incoming connections.
       */
      class NativeCrashReporter extends Thread {
          ProcessRecord mApp;
          int mSignal;
          String mCrashReport;
  
          NativeCrashReporter(ProcessRecord app, int signal, String report) {
              super("NativeCrashReport");
              mApp = app;
              mSignal = signal;
              mCrashReport = report;
          }
  
          @Override
          public void run() {
              try {
                  CrashInfo ci = new CrashInfo();
                  ci.exceptionClassName = "Native crash";
                  ci.exceptionMessage = Os.strsignal(mSignal);
                  ci.throwFileName = "unknown";
                  ci.throwClassName = "unknown";
                  ci.throwMethodName = "unknown";
                  ci.stackTrace = mCrashReport;
  
                  if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()");
                  mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
                  if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned");
              } catch (Exception e) {
                  Slog.e(TAG, "Unable to report native crash", e);
              }
          }
      }
  
      /*
       * Daemon thread that accept()s incoming domain socket connections from debuggerd
       * and processes the crash dump that is passed through.
       */
      NativeCrashListener(ActivityManagerService am) {
          mAm = am;
      }
  
      @Override
      public void run() {
          final byte[] ackSignal = new byte[1];
  
          if (DEBUG) Slog.i(TAG, "Starting up");
  
          // The file system entity for this socket is created with 0777 perms, owned
          // by system:system. selinux restricts things so that only crash_dump can
          // access it.
          {
              File socketFile = new File(DEBUGGERD_SOCKET_PATH);
              if (socketFile.exists()) {
                  socketFile.delete();
              }
          }
  
          try {
              FileDescriptor serverFd = Os.socket(AF_UNIX, SOCK_STREAM, 0);
              final UnixSocketAddress sockAddr = UnixSocketAddress.createFileSystem(
                      DEBUGGERD_SOCKET_PATH);
              Os.bind(serverFd, sockAddr);
              Os.listen(serverFd, 1);
              Os.chmod(DEBUGGERD_SOCKET_PATH, 0777);
  
              while (true) {
                  FileDescriptor peerFd = null;
                  try {
                      if (MORE_DEBUG) Slog.v(TAG, "Waiting for debuggerd connection");
                      peerFd = Os.accept(serverFd, null /* peerAddress */);
                      if (MORE_DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd);
                      if (peerFd != null) {
                          // the reporting thread may take responsibility for
                          // acking the debugger; make sure we play along.
                          consumeNativeCrashData(peerFd);
                      }
                  } catch (Exception e) {
                      Slog.w(TAG, "Error handling connection", e);
                  } finally {
                      // Always ack crash_dump's connection to us.  The actual
                      // byte written is irrelevant.
                      if (peerFd != null) {
                          try {
                              Os.write(peerFd, ackSignal, 0, 1);
                          } catch (Exception e) {
                              /* we don't care about failures here */
                              if (MORE_DEBUG) {
                                  Slog.d(TAG, "Exception writing ack: " + e.getMessage());
                              }
                          }
                          try {
                              Os.close(peerFd);
                          } catch (ErrnoException e) {
                              if (MORE_DEBUG) {
                                  Slog.d(TAG, "Exception closing socket: " + e.getMessage());
                              }
                          }
                      }
                  }
              }
          } catch (Exception e) {
              Slog.e(TAG, "Unable to init native debug socket!", e);
          }
      }
  
      static int unpackInt(byte[] buf, int offset) {
          int b0, b1, b2, b3;
  
          b0 = ((int) buf[offset]) & 0xFF; // mask against sign extension
          b1 = ((int) buf[offset+1]) & 0xFF;
          b2 = ((int) buf[offset+2]) & 0xFF;
          b3 = ((int) buf[offset+3]) & 0xFF;
          return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
      }
  
      static int readExactly(FileDescriptor fd, byte[] buffer, int offset, int numBytes)
              throws ErrnoException, InterruptedIOException {
          int totalRead = 0;
          while (numBytes > 0) {
              int n = Os.read(fd, buffer, offset + totalRead, numBytes);
              if (n <= 0) {
                  if (DEBUG) {
                      Slog.w(TAG, "Needed " + numBytes + " but saw " + n);
                  }
                  return -1;  // premature EOF or timeout
              }
              numBytes -= n;
              totalRead += n;
          }
          return totalRead;
      }
  
      // Read a crash report from the connection
      void consumeNativeCrashData(FileDescriptor fd) {
          if (MORE_DEBUG) Slog.i(TAG, "debuggerd connected");
          final byte[] buf = new byte[4096];
          final ByteArrayOutputStream os = new ByteArrayOutputStream(4096);
  
          try {
              StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS);
              Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
              Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
  
              // The socket is guarded by an selinux neverallow rule that only
              // permits crash_dump to connect to it. This allows us to trust the
              // received values.
  
              // first, the pid and signal number
              int headerBytes = readExactly(fd, buf, 0, 8);
              if (headerBytes != 8) {
                  // protocol failure; give up
                  Slog.e(TAG, "Unable to read from debuggerd");
                  return;
              }
  
              int pid = unpackInt(buf, 0);
              int signal = unpackInt(buf, 4);
              if (DEBUG) {
                  Slog.v(TAG, "Read pid=" + pid + " signal=" + signal);
              }
  
              // now the text of the dump
              if (pid > 0) {
                  final ProcessRecord pr;
                  synchronized (mAm.mPidsSelfLocked) {
                      pr = mAm.mPidsSelfLocked.get(pid);
                  }
                  if (pr != null) {
                      // Don't attempt crash reporting for persistent apps
                      if (pr.isPersistent()) {
                          if (DEBUG) {
                              Slog.v(TAG, "Skipping report for persistent app " + pr);
                          }
                          return;
                      }
  
                      int bytes;
                      do {
                          // get some data
                          bytes = Os.read(fd, buf, 0, buf.length);
                          if (bytes > 0) {
                              if (MORE_DEBUG) {
                                  String s = new String(buf, 0, bytes, "UTF-8");
                                  Slog.v(TAG, "READ=" + bytes + "> " + s);
                              }
                              // did we just get the EOD null byte?
                              if (buf[bytes-1] == 0) {
                                  os.write(buf, 0, bytes-1);  // exclude the EOD token
                                  break;
                              }
                              // no EOD, so collect it and read more
                              os.write(buf, 0, bytes);
                          }
                      } while (bytes > 0);
  
                      // Okay, we've got the report.
                      if (DEBUG) Slog.v(TAG, "processing");
  
                      // Mark the process record as being a native crash so that the
                      // cleanup mechanism knows we're still submitting the report
                      // even though the process will vanish as soon as we let
                      // debuggerd proceed.
                      synchronized (mAm) {
                          pr.setCrashing(true);
                          pr.forceCrashReport = true;
                      }
  
                      // Crash reporting is synchronous but we want to let debuggerd
                      // go about it business right away, so we spin off the actual
                      // reporting logic on a thread and let it take it's time.
                      final String reportString = new String(os.toByteArray(), "UTF-8");
                      (new NativeCrashReporter(pr, signal, reportString)).start();
                  } else {
                      Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
                  }
              } else {
                  Slog.e(TAG, "Bogus pid!");
              }
          } catch (Exception e) {
              Slog.e(TAG, "Exception dealing with report", e);
              // ugh, fail.
          }
      }
  
  }
  