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.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    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
53    static final long SOCKET_TIMEOUT_MILLIS = 2000;  // 2 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 = 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()");
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() {
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 {
128                    if (MORE_DEBUG) Slog.v(TAG, "Waiting for debuggerd connection");
129                    peerFd = Libcore.os.accept(serverFd, peer);
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                                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);
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                    }
156                }
157            }
158        } catch (Exception e) {
159            Slog.e(TAG, "Unable to init native debug socket!", e);
160        }
161    }
162
163    static int unpackInt(byte[] buf, int offset) {
164        int b0, b1, b2, b3;
165
166        b0 = ((int) buf[offset]) & 0xFF; // mask against sign extension
167        b1 = ((int) buf[offset+1]) & 0xFF;
168        b2 = ((int) buf[offset+2]) & 0xFF;
169        b3 = ((int) buf[offset+3]) & 0xFF;
170        return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
171    }
172
173    static int readExactly(FileDescriptor fd, byte[] buffer, int offset, int numBytes)
174            throws ErrnoException {
175        int totalRead = 0;
176        while (numBytes > 0) {
177            int n = Libcore.os.read(fd, buffer, offset + totalRead, numBytes);
178            if (n <= 0) {
179                if (DEBUG) {
180                    Slog.w(TAG, "Needed " + numBytes + " but saw " + n);
181                }
182                return -1;  // premature EOF or timeout
183            }
184            numBytes -= n;
185            totalRead += n;
186        }
187        return totalRead;
188    }
189
190    // Read the crash report from the debuggerd connection
191    void consumeNativeCrashData(FileDescriptor fd) {
192        if (MORE_DEBUG) Slog.i(TAG, "debuggerd connected");
193        final byte[] buf = new byte[4096];
194        final ByteArrayOutputStream os = new ByteArrayOutputStream(4096);
195
196        try {
197            StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS);
198            Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
199            Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
200
201            // first, the pid and signal number
202            int headerBytes = readExactly(fd, buf, 0, 8);
203            if (headerBytes != 8) {
204                // protocol failure; give up
205                Slog.e(TAG, "Unable to read from debuggerd");
206                return;
207            }
208
209            int pid = unpackInt(buf, 0);
210            int signal = unpackInt(buf, 4);
211            if (DEBUG) {
212                Slog.v(TAG, "Read pid=" + pid + " signal=" + signal);
213            }
214
215            // now the text of the dump
216            if (pid > 0) {
217                final ProcessRecord pr;
218                synchronized (mAm.mPidsSelfLocked) {
219                    pr = mAm.mPidsSelfLocked.get(pid);
220                }
221                if (pr != null) {
222                    // Don't attempt crash reporting for persistent apps
223                    if (pr.persistent) {
224                        if (DEBUG) {
225                            Slog.v(TAG, "Skipping report for persistent app " + pr);
226                        }
227                        return;
228                    }
229
230                    int bytes;
231                    do {
232                        // get some data
233                        bytes = Libcore.os.read(fd, buf, 0, buf.length);
234                        if (bytes > 0) {
235                            if (MORE_DEBUG) {
236                                String s = new String(buf, 0, bytes, "UTF-8");
237                                Slog.v(TAG, "READ=" + bytes + "> " + s);
238                            }
239                            // did we just get the EOD null byte?
240                            if (buf[bytes-1] == 0) {
241                                os.write(buf, 0, bytes-1);  // exclude the EOD token
242                                break;
243                            }
244                            // no EOD, so collect it and read more
245                            os.write(buf, 0, bytes);
246                        }
247                    } while (bytes > 0);
248
249                    // Okay, we've got the report.
250                    if (DEBUG) Slog.v(TAG, "processing");
251
252                    // Mark the process record as being a native crash so that the
253                    // cleanup mechanism knows we're still submitting the report
254                    // even though the process will vanish as soon as we let
255                    // debuggerd proceed.
256                    synchronized (mAm) {
257                        pr.crashing = true;
258                        pr.forceCrashReport = true;
259                    }
260
261                    // Crash reporting is synchronous but we want to let debuggerd
262                    // go about it business right away, so we spin off the actual
263                    // reporting logic on a thread and let it take it's time.
264                    final String reportString = new String(os.toByteArray(), "UTF-8");
265                    (new NativeCrashReporter(pr, signal, reportString)).start();
266                } else {
267                    Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
268                }
269            } else {
270                Slog.e(TAG, "Bogus pid!");
271            }
272        } catch (Exception e) {
273            Slog.e(TAG, "Exception dealing with report", e);
274            // ugh, fail.
275        }
276    }
277
278}
279