blob: 0688c50e863c61a2060103eab90cebc193fc5799 [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 */
43class NativeCrashListener extends Thread {
44 static final String TAG = "NativeCrashListener";
45 static final boolean 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
52 static final long SOCKET_TIMEOUT_MILLIS = 1000; // 1 second
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 = Libcore.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()");
Eric Rowe88d842c2013-04-08 15:00:27 -070085 mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
Christopher Tate58d380d2013-03-19 13:10:03 -070086 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() {
98 mAm = ActivityManagerService.self();
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 0700 perms, owned
108 // by system:system. debuggerd runs as root, so is capable of connecting to
109 // it, but 3rd party apps cannot.
110 {
111 File socketFile = new File(DEBUGGERD_SOCKET_PATH);
112 if (socketFile.exists()) {
113 socketFile.delete();
114 }
115 }
116
117 try {
118 FileDescriptor serverFd = Libcore.os.socket(AF_UNIX, SOCK_STREAM, 0);
119 final InetUnixAddress sockAddr = new InetUnixAddress(DEBUGGERD_SOCKET_PATH);
120 Libcore.os.bind(serverFd, sockAddr, 0);
121 Libcore.os.listen(serverFd, 1);
122
123 while (true) {
124 InetSocketAddress peer = new InetSocketAddress();
125 FileDescriptor peerFd = null;
126 try {
127 if (DEBUG) Slog.v(TAG, "Waiting for debuggerd connection");
128 peerFd = Libcore.os.accept(serverFd, peer);
129 if (DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd);
130 if (peerFd != null) {
131 // Only the superuser is allowed to talk to us over this socket
132 StructUcred credentials =
133 Libcore.os.getsockoptUcred(peerFd, SOL_SOCKET, SO_PEERCRED);
134 if (credentials.uid == 0) {
135 // the reporting thread may take responsibility for
136 // acking the debugger; make sure we play along.
137 consumeNativeCrashData(peerFd);
138 }
139 }
140 } catch (Exception e) {
141 Slog.w(TAG, "Error handling connection", e);
142 } finally {
143 // Always ack debuggerd's connection to us. The actual
144 // byte written is irrelevant.
145 if (peerFd != null) {
146 try {
147 Libcore.os.write(peerFd, ackSignal, 0, 1);
148 } catch (Exception e) { /* we don't care about failures here */ }
149 }
150 }
151 }
152 } catch (Exception e) {
153 Slog.e(TAG, "Unable to init native debug socket!", e);
154 }
155 }
156
157 static int unpackInt(byte[] buf, int offset) {
158 int b0, b1, b2, b3;
159
160 b0 = ((int) buf[offset]) & 0xFF; // mask against sign extension
161 b1 = ((int) buf[offset+1]) & 0xFF;
162 b2 = ((int) buf[offset+2]) & 0xFF;
163 b3 = ((int) buf[offset+3]) & 0xFF;
164 return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
165 }
166
167 static int readExactly(FileDescriptor fd, byte[] buffer, int offset, int numBytes)
168 throws ErrnoException {
169 int totalRead = 0;
170 while (numBytes > 0) {
171 int n = Libcore.os.read(fd, buffer, offset + totalRead, numBytes);
172 if (n <= 0) {
173 if (DEBUG) {
174 Slog.w(TAG, "Needed " + numBytes + " but saw " + n);
175 }
176 return -1; // premature EOF or timeout
177 }
178 numBytes -= n;
179 totalRead += n;
180 }
181 return totalRead;
182 }
183
184 // Read the crash report from the debuggerd connection
185 void consumeNativeCrashData(FileDescriptor fd) {
186 if (DEBUG) Slog.i(TAG, "debuggerd connected");
187 final byte[] buf = new byte[4096];
188 final ByteArrayOutputStream os = new ByteArrayOutputStream(4096);
189
190 try {
191 StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS);
192 Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
193 Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
194
195 // first, the pid and signal number
196 int headerBytes = readExactly(fd, buf, 0, 8);
197 if (headerBytes != 8) {
198 // protocol failure; give up
199 Slog.e(TAG, "Unable to read from debuggerd");
200 return;
201 }
202
203 int pid = unpackInt(buf, 0);
204 int signal = unpackInt(buf, 4);
205 if (DEBUG) {
206 Slog.v(TAG, "Read pid=" + pid + " signal=" + signal);
207 }
208
209 // now the text of the dump
210 if (pid > 0) {
211 final ProcessRecord pr;
212 synchronized (mAm.mPidsSelfLocked) {
213 pr = mAm.mPidsSelfLocked.get(pid);
214 }
215 if (pr != null) {
216 int bytes;
217 do {
218 // get some data
219 bytes = Libcore.os.read(fd, buf, 0, buf.length);
220 if (bytes > 0) {
221 if (DEBUG) {
222 String s = new String(buf, 0, bytes, "UTF-8");
223 Slog.v(TAG, "READ=" + bytes + "> " + s);
224 }
225 // did we just get the EOD null byte?
226 if (buf[bytes-1] == 0) {
227 os.write(buf, 0, bytes-1); // exclude the EOD token
228 break;
229 }
230 // no EOD, so collect it and read more
231 os.write(buf, 0, bytes);
232 }
233 } while (bytes > 0);
234
235 // Okay, we've got the report.
236 if (DEBUG) Slog.v(TAG, "processing");
237
238 // Mark the process record as being a native crash so that the
239 // cleanup mechanism knows we're still submitting the report
240 // even though the process will vanish as soon as we let
241 // debuggerd proceed.
242 synchronized (mAm) {
243 pr.crashing = true;
244 pr.forceCrashReport = true;
245 }
246
247 // Crash reporting is synchronous but we want to let debuggerd
248 // go about it business right away, so we spin off the actual
249 // reporting logic on a thread and let it take it's time.
250 final String reportString = new String(os.toByteArray(), "UTF-8");
251 (new NativeCrashReporter(pr, signal, reportString)).start();
252 } else {
253 Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
254 }
255 } else {
256 Slog.e(TAG, "Bogus pid!");
257 }
258 } catch (Exception e) {
259 Slog.e(TAG, "Exception dealing with report", e);
260 // ugh, fail.
261 }
262 }
263
264}