Home | History | Annotate | Download | only in am
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.am;
     18 
     19 import android.app.ApplicationErrorReport.CrashInfo;
     20 import android.system.ErrnoException;
     21 import android.system.Os;
     22 import android.system.StructTimeval;
     23 import android.system.UnixSocketAddress;
     24 import android.util.Slog;
     25 
     26 import static android.system.OsConstants.*;
     27 
     28 import java.io.ByteArrayOutputStream;
     29 import java.io.File;
     30 import java.io.FileDescriptor;
     31 import java.io.InterruptedIOException;
     32 import java.net.InetSocketAddress;
     33 
     34 /**
     35  * Set up a Unix domain socket that debuggerd will connect() to in
     36  * order to write a description of a native crash.  The crash info is
     37  * then parsed and forwarded to the ActivityManagerService's normal
     38  * crash handling code.
     39  *
     40  * Note that this component runs in a separate thread.
     41  */
     42 final class NativeCrashListener extends Thread {
     43     static final String TAG = "NativeCrashListener";
     44     static final boolean DEBUG = false;
     45     static final boolean MORE_DEBUG = DEBUG && false;
     46 
     47     // Must match the path defined in debuggerd.c.
     48     static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket";
     49 
     50     // Use a short timeout on socket operations and abandon the connection
     51     // on hard errors, just in case debuggerd goes out to lunch.
     52     static final long SOCKET_TIMEOUT_MILLIS = 10000;  // 10 seconds
     53 
     54     final ActivityManagerService mAm;
     55 
     56     /*
     57      * Spin the actual work of handling a debuggerd crash report into a
     58      * separate thread so that the listener can go immediately back to
     59      * accepting incoming connections.
     60      */
     61     class NativeCrashReporter extends Thread {
     62         ProcessRecord mApp;
     63         int mSignal;
     64         String mCrashReport;
     65 
     66         NativeCrashReporter(ProcessRecord app, int signal, String report) {
     67             super("NativeCrashReport");
     68             mApp = app;
     69             mSignal = signal;
     70             mCrashReport = report;
     71         }
     72 
     73         @Override
     74         public void run() {
     75             try {
     76                 CrashInfo ci = new CrashInfo();
     77                 ci.exceptionClassName = "Native crash";
     78                 ci.exceptionMessage = Os.strsignal(mSignal);
     79                 ci.throwFileName = "unknown";
     80                 ci.throwClassName = "unknown";
     81                 ci.throwMethodName = "unknown";
     82                 ci.stackTrace = mCrashReport;
     83 
     84                 if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()");
     85                 mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
     86                 if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned");
     87             } catch (Exception e) {
     88                 Slog.e(TAG, "Unable to report native crash", e);
     89             }
     90         }
     91     }
     92 
     93     /*
     94      * Daemon thread that accept()s incoming domain socket connections from debuggerd
     95      * and processes the crash dump that is passed through.
     96      */
     97     NativeCrashListener(ActivityManagerService am) {
     98         mAm = am;
     99     }
    100 
    101     @Override
    102     public void run() {
    103         final byte[] ackSignal = new byte[1];
    104 
    105         if (DEBUG) Slog.i(TAG, "Starting up");
    106 
    107         // The file system entity for this socket is created with 0777 perms, owned
    108         // by system:system. selinux restricts things so that only crash_dump can
    109         // access it.
    110         {
    111             File socketFile = new File(DEBUGGERD_SOCKET_PATH);
    112             if (socketFile.exists()) {
    113                 socketFile.delete();
    114             }
    115         }
    116 
    117         try {
    118             FileDescriptor serverFd = Os.socket(AF_UNIX, SOCK_STREAM, 0);
    119             final UnixSocketAddress sockAddr = UnixSocketAddress.createFileSystem(
    120                     DEBUGGERD_SOCKET_PATH);
    121             Os.bind(serverFd, sockAddr);
    122             Os.listen(serverFd, 1);
    123             Os.chmod(DEBUGGERD_SOCKET_PATH, 0777);
    124 
    125             while (true) {
    126                 FileDescriptor peerFd = null;
    127                 try {
    128                     if (MORE_DEBUG) Slog.v(TAG, "Waiting for debuggerd connection");
    129                     peerFd = Os.accept(serverFd, null /* peerAddress */);
    130                     if (MORE_DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd);
    131                     if (peerFd != null) {
    132                         // the reporting thread may take responsibility for
    133                         // acking the debugger; make sure we play along.
    134                         consumeNativeCrashData(peerFd);
    135                     }
    136                 } catch (Exception e) {
    137                     Slog.w(TAG, "Error handling connection", e);
    138                 } finally {
    139                     // Always ack crash_dump's connection to us.  The actual
    140                     // byte written is irrelevant.
    141                     if (peerFd != null) {
    142                         try {
    143                             Os.write(peerFd, ackSignal, 0, 1);
    144                         } catch (Exception e) {
    145                             /* we don't care about failures here */
    146                             if (MORE_DEBUG) {
    147                                 Slog.d(TAG, "Exception writing ack: " + e.getMessage());
    148                             }
    149                         }
    150                         try {
    151                             Os.close(peerFd);
    152                         } catch (ErrnoException e) {
    153                             if (MORE_DEBUG) {
    154                                 Slog.d(TAG, "Exception closing socket: " + e.getMessage());
    155                             }
    156                         }
    157                     }
    158                 }
    159             }
    160         } catch (Exception e) {
    161             Slog.e(TAG, "Unable to init native debug socket!", e);
    162         }
    163     }
    164 
    165     static int unpackInt(byte[] buf, int offset) {
    166         int b0, b1, b2, b3;
    167 
    168         b0 = ((int) buf[offset]) & 0xFF; // mask against sign extension
    169         b1 = ((int) buf[offset+1]) & 0xFF;
    170         b2 = ((int) buf[offset+2]) & 0xFF;
    171         b3 = ((int) buf[offset+3]) & 0xFF;
    172         return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
    173     }
    174 
    175     static int readExactly(FileDescriptor fd, byte[] buffer, int offset, int numBytes)
    176             throws ErrnoException, InterruptedIOException {
    177         int totalRead = 0;
    178         while (numBytes > 0) {
    179             int n = Os.read(fd, buffer, offset + totalRead, numBytes);
    180             if (n <= 0) {
    181                 if (DEBUG) {
    182                     Slog.w(TAG, "Needed " + numBytes + " but saw " + n);
    183                 }
    184                 return -1;  // premature EOF or timeout
    185             }
    186             numBytes -= n;
    187             totalRead += n;
    188         }
    189         return totalRead;
    190     }
    191 
    192     // Read a crash report from the connection
    193     void consumeNativeCrashData(FileDescriptor fd) {
    194         if (MORE_DEBUG) Slog.i(TAG, "debuggerd connected");
    195         final byte[] buf = new byte[4096];
    196         final ByteArrayOutputStream os = new ByteArrayOutputStream(4096);
    197 
    198         try {
    199             StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS);
    200             Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
    201             Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
    202 
    203             // The socket is guarded by an selinux neverallow rule that only
    204             // permits crash_dump to connect to it. This allows us to trust the
    205             // received values.
    206 
    207             // first, the pid and signal number
    208             int headerBytes = readExactly(fd, buf, 0, 8);
    209             if (headerBytes != 8) {
    210                 // protocol failure; give up
    211                 Slog.e(TAG, "Unable to read from debuggerd");
    212                 return;
    213             }
    214 
    215             int pid = unpackInt(buf, 0);
    216             int signal = unpackInt(buf, 4);
    217             if (DEBUG) {
    218                 Slog.v(TAG, "Read pid=" + pid + " signal=" + signal);
    219             }
    220 
    221             // now the text of the dump
    222             if (pid > 0) {
    223                 final ProcessRecord pr;
    224                 synchronized (mAm.mPidsSelfLocked) {
    225                     pr = mAm.mPidsSelfLocked.get(pid);
    226                 }
    227                 if (pr != null) {
    228                     // Don't attempt crash reporting for persistent apps
    229                     if (pr.persistent) {
    230                         if (DEBUG) {
    231                             Slog.v(TAG, "Skipping report for persistent app " + pr);
    232                         }
    233                         return;
    234                     }
    235 
    236                     int bytes;
    237                     do {
    238                         // get some data
    239                         bytes = Os.read(fd, buf, 0, buf.length);
    240                         if (bytes > 0) {
    241                             if (MORE_DEBUG) {
    242                                 String s = new String(buf, 0, bytes, "UTF-8");
    243                                 Slog.v(TAG, "READ=" + bytes + "> " + s);
    244                             }
    245                             // did we just get the EOD null byte?
    246                             if (buf[bytes-1] == 0) {
    247                                 os.write(buf, 0, bytes-1);  // exclude the EOD token
    248                                 break;
    249                             }
    250                             // no EOD, so collect it and read more
    251                             os.write(buf, 0, bytes);
    252                         }
    253                     } while (bytes > 0);
    254 
    255                     // Okay, we've got the report.
    256                     if (DEBUG) Slog.v(TAG, "processing");
    257 
    258                     // Mark the process record as being a native crash so that the
    259                     // cleanup mechanism knows we're still submitting the report
    260                     // even though the process will vanish as soon as we let
    261                     // debuggerd proceed.
    262                     synchronized (mAm) {
    263                         pr.crashing = true;
    264                         pr.forceCrashReport = true;
    265                     }
    266 
    267                     // Crash reporting is synchronous but we want to let debuggerd
    268                     // go about it business right away, so we spin off the actual
    269                     // reporting logic on a thread and let it take it's time.
    270                     final String reportString = new String(os.toByteArray(), "UTF-8");
    271                     (new NativeCrashReporter(pr, signal, reportString)).start();
    272                 } else {
    273                     Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
    274                 }
    275             } else {
    276                 Slog.e(TAG, "Bogus pid!");
    277             }
    278         } catch (Exception e) {
    279             Slog.e(TAG, "Exception dealing with report", e);
    280             // ugh, fail.
    281         }
    282     }
    283 
    284 }
    285