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