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