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