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