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