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.system.UnixSocketAddress; 25 import android.util.Slog; 26 27 import static android.system.OsConstants.*; 28 29 import java.io.ByteArrayOutputStream; 30 import java.io.File; 31 import java.io.FileDescriptor; 32 import java.io.InterruptedIOException; 33 import java.net.InetSocketAddress; 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, just in case debuggerd goes out to lunch. 53 static final long SOCKET_TIMEOUT_MILLIS = 10000; // 10 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 UnixSocketAddress sockAddr = UnixSocketAddress.createFileSystem( 121 DEBUGGERD_SOCKET_PATH); 122 Os.bind(serverFd, sockAddr); 123 Os.listen(serverFd, 1); 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 // 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