blob: 2c7f1f13098bb982e8366fac1332526a17cc18b7 [file] [log] [blame]
Christopher Tate58d380d2013-03-19 13:10:03 -07001/*
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
17package com.android.server.am;
18
19import android.app.ApplicationErrorReport.CrashInfo;
20import android.util.Slog;
21
22import libcore.io.ErrnoException;
23import libcore.io.Libcore;
24import libcore.io.StructTimeval;
25import libcore.io.StructUcred;
26
27import static libcore.io.OsConstants.*;
28
29import java.io.ByteArrayOutputStream;
30import java.io.File;
31import java.io.FileDescriptor;
32import java.net.InetSocketAddress;
33import 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 */
Dianne Hackbornbe4e6aa2013-06-07 13:25:29 -070043final class NativeCrashListener extends Thread {
Christopher Tate58d380d2013-03-19 13:10:03 -070044 static final String TAG = "NativeCrashListener";
45 static final boolean DEBUG = false;
Christopher Tate1b645982013-04-16 16:08:12 -070046 static final boolean MORE_DEBUG = DEBUG && false;
Christopher Tate58d380d2013-03-19 13:10:03 -070047
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
Christopher Tate1b645982013-04-16 16:08:12 -070053 static final long SOCKET_TIMEOUT_MILLIS = 2000; // 2 seconds
Christopher Tate58d380d2013-03-19 13:10:03 -070054
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 = Libcore.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()");
Eric Rowe88d842c2013-04-08 15:00:27 -070086 mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
Christopher Tate58d380d2013-03-19 13:10:03 -070087 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() {
99 mAm = ActivityManagerService.self();
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 = Libcore.os.socket(AF_UNIX, SOCK_STREAM, 0);
120 final InetUnixAddress sockAddr = new InetUnixAddress(DEBUGGERD_SOCKET_PATH);
121 Libcore.os.bind(serverFd, sockAddr, 0);
122 Libcore.os.listen(serverFd, 1);
123
124 while (true) {
125 InetSocketAddress peer = new InetSocketAddress();
126 FileDescriptor peerFd = null;
127 try {
Christopher Tate1b645982013-04-16 16:08:12 -0700128 if (MORE_DEBUG) Slog.v(TAG, "Waiting for debuggerd connection");
Christopher Tate58d380d2013-03-19 13:10:03 -0700129 peerFd = Libcore.os.accept(serverFd, peer);
Christopher Tate1b645982013-04-16 16:08:12 -0700130 if (MORE_DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd);
Christopher Tate58d380d2013-03-19 13:10:03 -0700131 if (peerFd != null) {
132 // Only the superuser is allowed to talk to us over this socket
133 StructUcred credentials =
134 Libcore.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 Libcore.os.write(peerFd, ackSignal, 0, 1);
Christopher Tate1b645982013-04-16 16:08:12 -0700149 } 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 }
Nick Kralevich1b2d5952013-06-18 17:56:28 -0700155 try {
156 Libcore.os.close(peerFd);
157 } catch (ErrnoException e) {
158 if (MORE_DEBUG) {
159 Slog.d(TAG, "Exception closing socket: " + e.getMessage());
160 }
161 }
Christopher Tate58d380d2013-03-19 13:10:03 -0700162 }
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 {
182 int totalRead = 0;
183 while (numBytes > 0) {
184 int n = Libcore.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) {
Christopher Tate1b645982013-04-16 16:08:12 -0700199 if (MORE_DEBUG) Slog.i(TAG, "debuggerd connected");
Christopher Tate58d380d2013-03-19 13:10:03 -0700200 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 Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
206 Libcore.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) {
Christopher Tateb86d81d2013-04-19 17:36:54 -0700229 // 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
Christopher Tate58d380d2013-03-19 13:10:03 -0700237 int bytes;
238 do {
239 // get some data
240 bytes = Libcore.os.read(fd, buf, 0, buf.length);
241 if (bytes > 0) {
Christopher Tate1b645982013-04-16 16:08:12 -0700242 if (MORE_DEBUG) {
Christopher Tate58d380d2013-03-19 13:10:03 -0700243 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}