ZygoteInit.java revision 42a4bb5730266f80585e67262c73505d0bfffbf8
1/*
2 * Copyright (C) 2007 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.internal.os;
18
19import static libcore.io.OsConstants.S_IRWXG;
20import static libcore.io.OsConstants.S_IRWXO;
21
22import android.content.res.Resources;
23import android.content.res.TypedArray;
24import android.net.LocalServerSocket;
25import android.os.Debug;
26import android.os.Process;
27import android.os.SystemClock;
28import android.os.Trace;
29import android.util.EventLog;
30import android.util.Log;
31
32import dalvik.system.VMRuntime;
33import dalvik.system.Zygote;
34
35import libcore.io.IoUtils;
36import libcore.io.Libcore;
37import libcore.io.OsConstants;
38
39import java.io.BufferedReader;
40import java.io.FileDescriptor;
41import java.io.IOException;
42import java.io.InputStream;
43import java.io.InputStreamReader;
44import java.lang.reflect.InvocationTargetException;
45import java.lang.reflect.Method;
46import java.lang.reflect.Modifier;
47import java.util.ArrayList;
48
49/**
50 * Startup class for the zygote process.
51 *
52 * Pre-initializes some classes, and then waits for commands on a UNIX domain
53 * socket. Based on these commands, forks off child processes that inherit
54 * the initial state of the VM.
55 *
56 * Please see {@link ZygoteConnection.Arguments} for documentation on the
57 * client protocol.
58 *
59 * @hide
60 */
61public class ZygoteInit {
62
63    private static final String TAG = "Zygote";
64
65    private static final String ANDROID_SOCKET_ENV = "ANDROID_SOCKET_zygote";
66
67    private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
68    private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030;
69
70    /** when preloading, GC after allocating this many bytes */
71    private static final int PRELOAD_GC_THRESHOLD = 50000;
72
73    public static final String USAGE_STRING =
74            " <\"start-system-server\"|\"\" for startSystemServer>";
75
76    private static LocalServerSocket sServerSocket;
77
78    /**
79     * Used to pre-load resources.  We hold a global reference on it so it
80     * never gets destroyed.
81     */
82    private static Resources mResources;
83
84    /**
85     * The number of times that the main Zygote loop
86     * should run before calling gc() again.
87     */
88    static final int GC_LOOP_COUNT = 10;
89
90    /**
91     * The name of a resource file that contains classes to preload.
92     */
93    private static final String PRELOADED_CLASSES = "preloaded-classes";
94
95    /** Controls whether we should preload resources during zygote init. */
96    private static final boolean PRELOAD_RESOURCES = true;
97
98    /**
99     * Invokes a static "main(argv[]) method on class "className".
100     * Converts various failing exceptions into RuntimeExceptions, with
101     * the assumption that they will then cause the VM instance to exit.
102     *
103     * @param loader class loader to use
104     * @param className Fully-qualified class name
105     * @param argv Argument vector for main()
106     */
107    static void invokeStaticMain(ClassLoader loader,
108            String className, String[] argv)
109            throws ZygoteInit.MethodAndArgsCaller {
110        Class<?> cl;
111
112        try {
113            cl = loader.loadClass(className);
114        } catch (ClassNotFoundException ex) {
115            throw new RuntimeException(
116                    "Missing class when invoking static main " + className,
117                    ex);
118        }
119
120        Method m;
121        try {
122            m = cl.getMethod("main", new Class[] { String[].class });
123        } catch (NoSuchMethodException ex) {
124            throw new RuntimeException(
125                    "Missing static main on " + className, ex);
126        } catch (SecurityException ex) {
127            throw new RuntimeException(
128                    "Problem getting static main on " + className, ex);
129        }
130
131        int modifiers = m.getModifiers();
132        if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
133            throw new RuntimeException(
134                    "Main method is not public and static on " + className);
135        }
136
137        /*
138         * This throw gets caught in ZygoteInit.main(), which responds
139         * by invoking the exception's run() method. This arrangement
140         * clears up all the stack frames that were required in setting
141         * up the process.
142         */
143        throw new ZygoteInit.MethodAndArgsCaller(m, argv);
144    }
145
146    /**
147     * Registers a server socket for zygote command connections
148     *
149     * @throws RuntimeException when open fails
150     */
151    private static void registerZygoteSocket() {
152        if (sServerSocket == null) {
153            int fileDesc;
154            try {
155                String env = System.getenv(ANDROID_SOCKET_ENV);
156                fileDesc = Integer.parseInt(env);
157            } catch (RuntimeException ex) {
158                throw new RuntimeException(
159                        ANDROID_SOCKET_ENV + " unset or invalid", ex);
160            }
161
162            try {
163                sServerSocket = new LocalServerSocket(
164                        createFileDescriptor(fileDesc));
165            } catch (IOException ex) {
166                throw new RuntimeException(
167                        "Error binding to local socket '" + fileDesc + "'", ex);
168            }
169        }
170    }
171
172    /**
173     * Waits for and accepts a single command connection. Throws
174     * RuntimeException on failure.
175     */
176    private static ZygoteConnection acceptCommandPeer() {
177        try {
178            return new ZygoteConnection(sServerSocket.accept());
179        } catch (IOException ex) {
180            throw new RuntimeException(
181                    "IOException during accept()", ex);
182        }
183    }
184
185    /**
186     * Close and clean up zygote sockets. Called on shutdown and on the
187     * child's exit path.
188     */
189    static void closeServerSocket() {
190        try {
191            if (sServerSocket != null) {
192                sServerSocket.close();
193            }
194        } catch (IOException ex) {
195            Log.e(TAG, "Zygote:  error closing sockets", ex);
196        }
197
198        sServerSocket = null;
199    }
200
201    private static final int UNPRIVILEGED_UID = 9999;
202    private static final int UNPRIVILEGED_GID = 9999;
203
204    private static final int ROOT_UID = 0;
205    private static final int ROOT_GID = 0;
206
207    /**
208     * Sets effective user ID.
209     */
210    private static void setEffectiveUser(int uid) {
211        int errno = setreuid(ROOT_UID, uid);
212        if (errno != 0) {
213            Log.e(TAG, "setreuid() failed. errno: " + errno);
214        }
215    }
216
217    /**
218     * Sets effective group ID.
219     */
220    private static void setEffectiveGroup(int gid) {
221        int errno = setregid(ROOT_GID, gid);
222        if (errno != 0) {
223            Log.e(TAG, "setregid() failed. errno: " + errno);
224        }
225    }
226
227    static void preload() {
228        preloadClasses();
229        preloadResources();
230    }
231
232    /**
233     * Performs Zygote process initialization. Loads and initializes
234     * commonly used classes.
235     *
236     * Most classes only cause a few hundred bytes to be allocated, but
237     * a few will allocate a dozen Kbytes (in one case, 500+K).
238     */
239    private static void preloadClasses() {
240        final VMRuntime runtime = VMRuntime.getRuntime();
241
242        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(
243                PRELOADED_CLASSES);
244        if (is == null) {
245            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
246        } else {
247            Log.i(TAG, "Preloading classes...");
248            long startTime = SystemClock.uptimeMillis();
249
250            // Drop root perms while running static initializers.
251            setEffectiveGroup(UNPRIVILEGED_GID);
252            setEffectiveUser(UNPRIVILEGED_UID);
253
254            // Alter the target heap utilization.  With explicit GCs this
255            // is not likely to have any effect.
256            float defaultUtilization = runtime.getTargetHeapUtilization();
257            runtime.setTargetHeapUtilization(0.8f);
258
259            // Start with a clean slate.
260            System.gc();
261            runtime.runFinalizationSync();
262            Debug.startAllocCounting();
263
264            try {
265                BufferedReader br
266                    = new BufferedReader(new InputStreamReader(is), 256);
267
268                int count = 0;
269                String line;
270                while ((line = br.readLine()) != null) {
271                    // Skip comments and blank lines.
272                    line = line.trim();
273                    if (line.startsWith("#") || line.equals("")) {
274                        continue;
275                    }
276
277                    try {
278                        if (false) {
279                            Log.v(TAG, "Preloading " + line + "...");
280                        }
281                        Class.forName(line);
282                        if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
283                            if (false) {
284                                Log.v(TAG,
285                                    " GC at " + Debug.getGlobalAllocSize());
286                            }
287                            System.gc();
288                            runtime.runFinalizationSync();
289                            Debug.resetGlobalAllocSize();
290                        }
291                        count++;
292                    } catch (ClassNotFoundException e) {
293                        Log.w(TAG, "Class not found for preloading: " + line);
294                    } catch (UnsatisfiedLinkError e) {
295                        Log.w(TAG, "Problem preloading " + line + ": " + e);
296                    } catch (Throwable t) {
297                        Log.e(TAG, "Error preloading " + line + ".", t);
298                        if (t instanceof Error) {
299                            throw (Error) t;
300                        }
301                        if (t instanceof RuntimeException) {
302                            throw (RuntimeException) t;
303                        }
304                        throw new RuntimeException(t);
305                    }
306                }
307
308                Log.i(TAG, "...preloaded " + count + " classes in "
309                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
310            } catch (IOException e) {
311                Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
312            } finally {
313                IoUtils.closeQuietly(is);
314                // Restore default.
315                runtime.setTargetHeapUtilization(defaultUtilization);
316
317                Debug.stopAllocCounting();
318
319                // Bring back root. We'll need it later.
320                setEffectiveUser(ROOT_UID);
321                setEffectiveGroup(ROOT_GID);
322            }
323        }
324    }
325
326    /**
327     * Load in commonly used resources, so they can be shared across
328     * processes.
329     *
330     * These tend to be a few Kbytes, but are frequently in the 20-40K
331     * range, and occasionally even larger.
332     */
333    private static void preloadResources() {
334        final VMRuntime runtime = VMRuntime.getRuntime();
335
336        Debug.startAllocCounting();
337        try {
338            System.gc();
339            runtime.runFinalizationSync();
340            mResources = Resources.getSystem();
341            mResources.startPreloading();
342            if (PRELOAD_RESOURCES) {
343                Log.i(TAG, "Preloading resources...");
344
345                long startTime = SystemClock.uptimeMillis();
346                TypedArray ar = mResources.obtainTypedArray(
347                        com.android.internal.R.array.preloaded_drawables);
348                int N = preloadDrawables(runtime, ar);
349                ar.recycle();
350                Log.i(TAG, "...preloaded " + N + " resources in "
351                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
352
353                startTime = SystemClock.uptimeMillis();
354                ar = mResources.obtainTypedArray(
355                        com.android.internal.R.array.preloaded_color_state_lists);
356                N = preloadColorStateLists(runtime, ar);
357                ar.recycle();
358                Log.i(TAG, "...preloaded " + N + " resources in "
359                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
360            }
361            mResources.finishPreloading();
362        } catch (RuntimeException e) {
363            Log.w(TAG, "Failure preloading resources", e);
364        } finally {
365            Debug.stopAllocCounting();
366        }
367    }
368
369    private static int preloadColorStateLists(VMRuntime runtime, TypedArray ar) {
370        int N = ar.length();
371        for (int i=0; i<N; i++) {
372            if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
373                if (false) {
374                    Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
375                }
376                System.gc();
377                runtime.runFinalizationSync();
378                Debug.resetGlobalAllocSize();
379            }
380            int id = ar.getResourceId(i, 0);
381            if (false) {
382                Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
383            }
384            if (id != 0) {
385                if (mResources.getColorStateList(id) == null) {
386                    throw new IllegalArgumentException(
387                            "Unable to find preloaded color resource #0x"
388                            + Integer.toHexString(id)
389                            + " (" + ar.getString(i) + ")");
390                }
391            }
392        }
393        return N;
394    }
395
396
397    private static int preloadDrawables(VMRuntime runtime, TypedArray ar) {
398        int N = ar.length();
399        for (int i=0; i<N; i++) {
400            if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
401                if (false) {
402                    Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
403                }
404                System.gc();
405                runtime.runFinalizationSync();
406                Debug.resetGlobalAllocSize();
407            }
408            int id = ar.getResourceId(i, 0);
409            if (false) {
410                Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
411            }
412            if (id != 0) {
413                if (mResources.getDrawable(id) == null) {
414                    throw new IllegalArgumentException(
415                            "Unable to find preloaded drawable resource #0x"
416                            + Integer.toHexString(id)
417                            + " (" + ar.getString(i) + ")");
418                }
419            }
420        }
421        return N;
422    }
423
424    /**
425     * Runs several special GCs to try to clean up a few generations of
426     * softly- and final-reachable objects, along with any other garbage.
427     * This is only useful just before a fork().
428     */
429    /*package*/ static void gc() {
430        final VMRuntime runtime = VMRuntime.getRuntime();
431
432        /* runFinalizationSync() lets finalizers be called in Zygote,
433         * which doesn't have a HeapWorker thread.
434         */
435        System.gc();
436        runtime.runFinalizationSync();
437        System.gc();
438        runtime.runFinalizationSync();
439        System.gc();
440        runtime.runFinalizationSync();
441    }
442
443    /**
444     * Finish remaining work for the newly forked system server process.
445     */
446    private static void handleSystemServerProcess(
447            ZygoteConnection.Arguments parsedArgs)
448            throws ZygoteInit.MethodAndArgsCaller {
449
450        closeServerSocket();
451
452        // set umask to 0077 so new files and directories will default to owner-only permissions.
453        Libcore.os.umask(S_IRWXG | S_IRWXO);
454
455        if (parsedArgs.niceName != null) {
456            Process.setArgV0(parsedArgs.niceName);
457        }
458
459        if (parsedArgs.invokeWith != null) {
460            WrapperInit.execApplication(parsedArgs.invokeWith,
461                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
462                    null, parsedArgs.remainingArgs);
463        } else {
464            /*
465             * Pass the remaining arguments to SystemServer.
466             */
467            RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs);
468        }
469
470        /* should never reach here */
471    }
472
473    /**
474     * Prepare the arguments and fork for the system server process.
475     */
476    private static boolean startSystemServer()
477            throws MethodAndArgsCaller, RuntimeException {
478        long capabilities = posixCapabilitiesAsBits(
479            OsConstants.CAP_KILL,
480            OsConstants.CAP_NET_ADMIN,
481            OsConstants.CAP_NET_BIND_SERVICE,
482            OsConstants.CAP_NET_BROADCAST,
483            OsConstants.CAP_NET_RAW,
484            OsConstants.CAP_SYS_BOOT,
485            OsConstants.CAP_SYS_MODULE,
486            OsConstants.CAP_SYS_NICE,
487            OsConstants.CAP_SYS_RESOURCE,
488            OsConstants.CAP_SYS_TIME,
489            OsConstants.CAP_SYS_TTY_CONFIG
490        );
491        /* Hardcoded command line to start the system server */
492        String args[] = {
493            "--setuid=1000",
494            "--setgid=1000",
495            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006,3007",
496            "--capabilities=" + capabilities + "," + capabilities,
497            "--runtime-init",
498            "--nice-name=system_server",
499            "com.android.server.SystemServer",
500        };
501        ZygoteConnection.Arguments parsedArgs = null;
502
503        int pid;
504
505        try {
506            parsedArgs = new ZygoteConnection.Arguments(args);
507            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
508            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
509
510            /* Request to fork the system server process */
511            pid = Zygote.forkSystemServer(
512                    parsedArgs.uid, parsedArgs.gid,
513                    parsedArgs.gids,
514                    parsedArgs.debugFlags,
515                    null,
516                    parsedArgs.permittedCapabilities,
517                    parsedArgs.effectiveCapabilities);
518        } catch (IllegalArgumentException ex) {
519            throw new RuntimeException(ex);
520        }
521
522        /* For child process */
523        if (pid == 0) {
524            handleSystemServerProcess(parsedArgs);
525        }
526
527        return true;
528    }
529
530    /**
531     * Gets the bit array representation of the provided list of POSIX capabilities.
532     */
533    private static long posixCapabilitiesAsBits(int... capabilities) {
534        long result = 0;
535        for (int capability : capabilities) {
536            if ((capability < 0) || (capability > OsConstants.CAP_LAST_CAP)) {
537                throw new IllegalArgumentException(String.valueOf(capability));
538            }
539            result |= (1L << capability);
540        }
541        return result;
542    }
543
544    public static void main(String argv[]) {
545        try {
546            // Start profiling the zygote initialization.
547            SamplingProfilerIntegration.start();
548
549            registerZygoteSocket();
550            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
551                SystemClock.uptimeMillis());
552            preload();
553            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
554                SystemClock.uptimeMillis());
555
556            // Finish profiling the zygote initialization.
557            SamplingProfilerIntegration.writeZygoteSnapshot();
558
559            // Do an initial gc to clean up after startup
560            gc();
561
562            // Disable tracing so that forked processes do not inherit stale tracing tags from
563            // Zygote.
564            Trace.setTracingEnabled(false);
565
566            // If requested, start system server directly from Zygote
567            if (argv.length != 2) {
568                throw new RuntimeException(argv[0] + USAGE_STRING);
569            }
570
571            if (argv[1].equals("start-system-server")) {
572                startSystemServer();
573            } else if (!argv[1].equals("")) {
574                throw new RuntimeException(argv[0] + USAGE_STRING);
575            }
576
577            Log.i(TAG, "Accepting command socket connections");
578
579            runSelectLoop();
580
581            closeServerSocket();
582        } catch (MethodAndArgsCaller caller) {
583            caller.run();
584        } catch (RuntimeException ex) {
585            Log.e(TAG, "Zygote died with exception", ex);
586            closeServerSocket();
587            throw ex;
588        }
589    }
590
591    /**
592     * Runs the zygote process's select loop. Accepts new connections as
593     * they happen, and reads commands from connections one spawn-request's
594     * worth at a time.
595     *
596     * @throws MethodAndArgsCaller in a child process when a main() should
597     * be executed.
598     */
599    private static void runSelectLoop() throws MethodAndArgsCaller {
600        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
601        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
602        FileDescriptor[] fdArray = new FileDescriptor[4];
603
604        fds.add(sServerSocket.getFileDescriptor());
605        peers.add(null);
606
607        int loopCount = GC_LOOP_COUNT;
608        while (true) {
609            int index;
610
611            /*
612             * Call gc() before we block in select().
613             * It's work that has to be done anyway, and it's better
614             * to avoid making every child do it.  It will also
615             * madvise() any free memory as a side-effect.
616             *
617             * Don't call it every time, because walking the entire
618             * heap is a lot of overhead to free a few hundred bytes.
619             */
620            if (loopCount <= 0) {
621                gc();
622                loopCount = GC_LOOP_COUNT;
623            } else {
624                loopCount--;
625            }
626
627
628            try {
629                fdArray = fds.toArray(fdArray);
630                index = selectReadable(fdArray);
631            } catch (IOException ex) {
632                throw new RuntimeException("Error in select()", ex);
633            }
634
635            if (index < 0) {
636                throw new RuntimeException("Error in select()");
637            } else if (index == 0) {
638                ZygoteConnection newPeer = acceptCommandPeer();
639                peers.add(newPeer);
640                fds.add(newPeer.getFileDesciptor());
641            } else {
642                boolean done;
643                done = peers.get(index).runOnce();
644
645                if (done) {
646                    peers.remove(index);
647                    fds.remove(index);
648                }
649            }
650        }
651    }
652
653    /**
654     * The Linux syscall "setreuid()"
655     * @param ruid real uid
656     * @param euid effective uid
657     * @return 0 on success, non-zero errno on fail
658     */
659    static native int setreuid(int ruid, int euid);
660
661    /**
662     * The Linux syscall "setregid()"
663     * @param rgid real gid
664     * @param egid effective gid
665     * @return 0 on success, non-zero errno on fail
666     */
667    static native int setregid(int rgid, int egid);
668
669    /**
670     * Invokes the linux syscall "setpgid"
671     *
672     * @param pid pid to change
673     * @param pgid new process group of pid
674     * @return 0 on success or non-zero errno on fail
675     */
676    static native int setpgid(int pid, int pgid);
677
678    /**
679     * Invokes the linux syscall "getpgid"
680     *
681     * @param pid pid to query
682     * @return pgid of pid in question
683     * @throws IOException on error
684     */
685    static native int getpgid(int pid) throws IOException;
686
687    /**
688     * Invokes the syscall dup2() to copy the specified descriptors into
689     * stdin, stdout, and stderr. The existing stdio descriptors will be
690     * closed and errors during close will be ignored. The specified
691     * descriptors will also remain open at their original descriptor numbers,
692     * so the caller may want to close the original descriptors.
693     *
694     * @param in new stdin
695     * @param out new stdout
696     * @param err new stderr
697     * @throws IOException
698     */
699    static native void reopenStdio(FileDescriptor in,
700            FileDescriptor out, FileDescriptor err) throws IOException;
701
702    /**
703     * Toggles the close-on-exec flag for the specified file descriptor.
704     *
705     * @param fd non-null; file descriptor
706     * @param flag desired close-on-exec flag state
707     * @throws IOException
708     */
709    static native void setCloseOnExec(FileDescriptor fd, boolean flag)
710            throws IOException;
711
712    /**
713     * Invokes select() on the provider array of file descriptors (selecting
714     * for readability only). Array elements of null are ignored.
715     *
716     * @param fds non-null; array of readable file descriptors
717     * @return index of descriptor that is now readable or -1 for empty array.
718     * @throws IOException if an error occurs
719     */
720    static native int selectReadable(FileDescriptor[] fds) throws IOException;
721
722    /**
723     * Creates a file descriptor from an int fd.
724     *
725     * @param fd integer OS file descriptor
726     * @return non-null; FileDescriptor instance
727     * @throws IOException if fd is invalid
728     */
729    static native FileDescriptor createFileDescriptor(int fd)
730            throws IOException;
731
732    /**
733     * Class not instantiable.
734     */
735    private ZygoteInit() {
736    }
737
738    /**
739     * Helper exception class which holds a method and arguments and
740     * can call them. This is used as part of a trampoline to get rid of
741     * the initial process setup stack frames.
742     */
743    public static class MethodAndArgsCaller extends Exception
744            implements Runnable {
745        /** method to call */
746        private final Method mMethod;
747
748        /** argument array */
749        private final String[] mArgs;
750
751        public MethodAndArgsCaller(Method method, String[] args) {
752            mMethod = method;
753            mArgs = args;
754        }
755
756        public void run() {
757            try {
758                mMethod.invoke(null, new Object[] { mArgs });
759            } catch (IllegalAccessException ex) {
760                throw new RuntimeException(ex);
761            } catch (InvocationTargetException ex) {
762                Throwable cause = ex.getCause();
763                if (cause instanceof RuntimeException) {
764                    throw (RuntimeException) cause;
765                } else if (cause instanceof Error) {
766                    throw (Error) cause;
767                }
768                throw new RuntimeException(ex);
769            }
770        }
771    }
772}
773