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.StructUcred;
24import android.system.UnixSocketAddress;
25import android.util.Slog;
26
27import static android.system.OsConstants.*;
28
29import java.io.ByteArrayOutputStream;
30import java.io.File;
31import java.io.FileDescriptor;
32import java.io.InterruptedIOException;
33import 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 */
43final 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