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
17package com.android.server.am;
18
19import android.app.ApplicationErrorReport.CrashInfo;
20import android.system.ErrnoException;
21import android.system.Os;
22import android.system.StructTimeval;
23import android.system.UnixSocketAddress;
24import android.util.Slog;
25
26import static android.system.OsConstants.*;
27
28import java.io.ByteArrayOutputStream;
29import java.io.File;
30import java.io.FileDescriptor;
31import java.io.InterruptedIOException;
32import java.net.InetSocketAddress;
33
34/**
35 * Set up a Unix domain socket that debuggerd will connect() to in
36 * order to write a description of a native crash.  The crash info is
37 * then parsed and forwarded to the ActivityManagerService's normal
38 * crash handling code.
39 *
40 * Note that this component runs in a separate thread.
41 */
42final class NativeCrashListener extends Thread {
43    static final String TAG = "NativeCrashListener";
44    static final boolean DEBUG = false;
45    static final boolean MORE_DEBUG = 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, just in case debuggerd goes out to lunch.
52    static final long SOCKET_TIMEOUT_MILLIS = 10000;  // 10 seconds
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 = 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()");
85                mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
86                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(ActivityManagerService am) {
98        mAm = am;
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 0777 perms, owned
108        // by system:system. selinux restricts things so that only crash_dump can
109        // access it.
110        {
111            File socketFile = new File(DEBUGGERD_SOCKET_PATH);
112            if (socketFile.exists()) {
113                socketFile.delete();
114            }
115        }
116
117        try {
118            FileDescriptor serverFd = Os.socket(AF_UNIX, SOCK_STREAM, 0);
119            final UnixSocketAddress sockAddr = UnixSocketAddress.createFileSystem(
120                    DEBUGGERD_SOCKET_PATH);
121            Os.bind(serverFd, sockAddr);
122            Os.listen(serverFd, 1);
123            Os.chmod(DEBUGGERD_SOCKET_PATH, 0777);
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                        // the reporting thread may take responsibility for
133                        // acking the debugger; make sure we play along.
134                        consumeNativeCrashData(peerFd);
135                    }
136                } catch (Exception e) {
137                    Slog.w(TAG, "Error handling connection", e);
138                } finally {
139                    // Always ack crash_dump's connection to us.  The actual
140                    // byte written is irrelevant.
141                    if (peerFd != null) {
142                        try {
143                            Os.write(peerFd, ackSignal, 0, 1);
144                        } catch (Exception e) {
145                            /* we don't care about failures here */
146                            if (MORE_DEBUG) {
147                                Slog.d(TAG, "Exception writing ack: " + e.getMessage());
148                            }
149                        }
150                        try {
151                            Os.close(peerFd);
152                        } catch (ErrnoException e) {
153                            if (MORE_DEBUG) {
154                                Slog.d(TAG, "Exception closing socket: " + e.getMessage());
155                            }
156                        }
157                    }
158                }
159            }
160        } catch (Exception e) {
161            Slog.e(TAG, "Unable to init native debug socket!", e);
162        }
163    }
164
165    static int unpackInt(byte[] buf, int offset) {
166        int b0, b1, b2, b3;
167
168        b0 = ((int) buf[offset]) & 0xFF; // mask against sign extension
169        b1 = ((int) buf[offset+1]) & 0xFF;
170        b2 = ((int) buf[offset+2]) & 0xFF;
171        b3 = ((int) buf[offset+3]) & 0xFF;
172        return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
173    }
174
175    static int readExactly(FileDescriptor fd, byte[] buffer, int offset, int numBytes)
176            throws ErrnoException, InterruptedIOException {
177        int totalRead = 0;
178        while (numBytes > 0) {
179            int n = Os.read(fd, buffer, offset + totalRead, numBytes);
180            if (n <= 0) {
181                if (DEBUG) {
182                    Slog.w(TAG, "Needed " + numBytes + " but saw " + n);
183                }
184                return -1;  // premature EOF or timeout
185            }
186            numBytes -= n;
187            totalRead += n;
188        }
189        return totalRead;
190    }
191
192    // Read a crash report from the connection
193    void consumeNativeCrashData(FileDescriptor fd) {
194        if (MORE_DEBUG) Slog.i(TAG, "debuggerd connected");
195        final byte[] buf = new byte[4096];
196        final ByteArrayOutputStream os = new ByteArrayOutputStream(4096);
197
198        try {
199            StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS);
200            Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
201            Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
202
203            // The socket is guarded by an selinux neverallow rule that only
204            // permits crash_dump to connect to it. This allows us to trust the
205            // received values.
206
207            // first, the pid and signal number
208            int headerBytes = readExactly(fd, buf, 0, 8);
209            if (headerBytes != 8) {
210                // protocol failure; give up
211                Slog.e(TAG, "Unable to read from debuggerd");
212                return;
213            }
214
215            int pid = unpackInt(buf, 0);
216            int signal = unpackInt(buf, 4);
217            if (DEBUG) {
218                Slog.v(TAG, "Read pid=" + pid + " signal=" + signal);
219            }
220
221            // now the text of the dump
222            if (pid > 0) {
223                final ProcessRecord pr;
224                synchronized (mAm.mPidsSelfLocked) {
225                    pr = mAm.mPidsSelfLocked.get(pid);
226                }
227                if (pr != null) {
228                    // Don't attempt crash reporting for persistent apps
229                    if (pr.persistent) {
230                        if (DEBUG) {
231                            Slog.v(TAG, "Skipping report for persistent app " + pr);
232                        }
233                        return;
234                    }
235
236                    int bytes;
237                    do {
238                        // get some data
239                        bytes = Os.read(fd, buf, 0, buf.length);
240                        if (bytes > 0) {
241                            if (MORE_DEBUG) {
242                                String s = new String(buf, 0, bytes, "UTF-8");
243                                Slog.v(TAG, "READ=" + bytes + "> " + s);
244                            }
245                            // did we just get the EOD null byte?
246                            if (buf[bytes-1] == 0) {
247                                os.write(buf, 0, bytes-1);  // exclude the EOD token
248                                break;
249                            }
250                            // no EOD, so collect it and read more
251                            os.write(buf, 0, bytes);
252                        }
253                    } while (bytes > 0);
254
255                    // Okay, we've got the report.
256                    if (DEBUG) Slog.v(TAG, "processing");
257
258                    // Mark the process record as being a native crash so that the
259                    // cleanup mechanism knows we're still submitting the report
260                    // even though the process will vanish as soon as we let
261                    // debuggerd proceed.
262                    synchronized (mAm) {
263                        pr.crashing = true;
264                        pr.forceCrashReport = true;
265                    }
266
267                    // Crash reporting is synchronous but we want to let debuggerd
268                    // go about it business right away, so we spin off the actual
269                    // reporting logic on a thread and let it take it's time.
270                    final String reportString = new String(os.toByteArray(), "UTF-8");
271                    (new NativeCrashReporter(pr, signal, reportString)).start();
272                } else {
273                    Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
274                }
275            } else {
276                Slog.e(TAG, "Bogus pid!");
277            }
278        } catch (Exception e) {
279            Slog.e(TAG, "Exception dealing with report", e);
280            // ugh, fail.
281        }
282    }
283
284}
285