1/*
2 * Copyright (C) 2008 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
17/*
18 * dalvik.system.Zygote
19 */
20#include "Dalvik.h"
21#include "native/InternalNativePriv.h"
22
23#include <selinux/android.h>
24
25#include <signal.h>
26#include <sys/types.h>
27#include <sys/wait.h>
28#include <grp.h>
29#include <errno.h>
30#include <paths.h>
31#include <sys/personality.h>
32#include <sys/stat.h>
33#include <sys/mount.h>
34#include <linux/fs.h>
35#include <cutils/fs.h>
36#include <cutils/sched_policy.h>
37#include <cutils/multiuser.h>
38#include <sched.h>
39#include <sys/utsname.h>
40#include <sys/capability.h>
41
42#if defined(HAVE_PRCTL)
43# include <sys/prctl.h>
44#endif
45
46#define ZYGOTE_LOG_TAG "Zygote"
47
48/* must match values in dalvik.system.Zygote */
49enum {
50    DEBUG_ENABLE_DEBUGGER           = 1,
51    DEBUG_ENABLE_CHECKJNI           = 1 << 1,
52    DEBUG_ENABLE_ASSERT             = 1 << 2,
53    DEBUG_ENABLE_SAFEMODE           = 1 << 3,
54    DEBUG_ENABLE_JNI_LOGGING        = 1 << 4,
55};
56
57/* must match values in dalvik.system.Zygote */
58enum {
59    MOUNT_EXTERNAL_NONE = 0,
60    MOUNT_EXTERNAL_SINGLEUSER = 1,
61    MOUNT_EXTERNAL_MULTIUSER = 2,
62    MOUNT_EXTERNAL_MULTIUSER_ALL = 3,
63};
64
65/*
66 * This signal handler is for zygote mode, since the zygote
67 * must reap its children
68 */
69static void sigchldHandler(int s)
70{
71    pid_t pid;
72    int status;
73
74    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
75        /* Log process-death status that we care about.  In general it is not
76           safe to call ALOG(...) from a signal handler because of possible
77           reentrancy.  However, we know a priori that the current implementation
78           of ALOG() is safe to call from a SIGCHLD handler in the zygote process.
79           If the ALOG() implementation changes its locking strategy or its use
80           of syscalls within the lazy-init critical section, its use here may
81           become unsafe. */
82        if (WIFEXITED(status)) {
83            if (WEXITSTATUS(status)) {
84                ALOG(LOG_DEBUG, ZYGOTE_LOG_TAG, "Process %d exited cleanly (%d)",
85                    (int) pid, WEXITSTATUS(status));
86            } else {
87                IF_ALOGV(/*should use ZYGOTE_LOG_TAG*/) {
88                    ALOG(LOG_VERBOSE, ZYGOTE_LOG_TAG,
89                        "Process %d exited cleanly (%d)",
90                        (int) pid, WEXITSTATUS(status));
91                }
92            }
93        } else if (WIFSIGNALED(status)) {
94            if (WTERMSIG(status) != SIGKILL) {
95                ALOG(LOG_DEBUG, ZYGOTE_LOG_TAG,
96                    "Process %d terminated by signal (%d)",
97                    (int) pid, WTERMSIG(status));
98            } else {
99                IF_ALOGV(/*should use ZYGOTE_LOG_TAG*/) {
100                    ALOG(LOG_VERBOSE, ZYGOTE_LOG_TAG,
101                        "Process %d terminated by signal (%d)",
102                        (int) pid, WTERMSIG(status));
103                }
104            }
105#ifdef WCOREDUMP
106            if (WCOREDUMP(status)) {
107                ALOG(LOG_INFO, ZYGOTE_LOG_TAG, "Process %d dumped core",
108                    (int) pid);
109            }
110#endif /* ifdef WCOREDUMP */
111        }
112
113        /*
114         * If the just-crashed process is the system_server, bring down zygote
115         * so that it is restarted by init and system server will be restarted
116         * from there.
117         */
118        if (pid == gDvm.systemServerPid) {
119            ALOG(LOG_INFO, ZYGOTE_LOG_TAG,
120                "Exit zygote because system server (%d) has terminated",
121                (int) pid);
122            kill(getpid(), SIGKILL);
123        }
124    }
125
126    if (pid < 0) {
127        ALOG(LOG_WARN, ZYGOTE_LOG_TAG,
128            "Zygote SIGCHLD error in waitpid: %s",strerror(errno));
129    }
130}
131
132/*
133 * configure sigchld handler for the zygote process
134 * This is configured very late, because earlier in the dalvik lifecycle
135 * we can fork() and exec() for the verifier/optimizer, and we
136 * want to waitpid() for those rather than have them be harvested immediately.
137 *
138 * This ends up being called repeatedly before each fork(), but there's
139 * no real harm in that.
140 */
141static void setSignalHandler()
142{
143    int err;
144    struct sigaction sa;
145
146    memset(&sa, 0, sizeof(sa));
147
148    sa.sa_handler = sigchldHandler;
149
150    err = sigaction (SIGCHLD, &sa, NULL);
151
152    if (err < 0) {
153        ALOGW("Error setting SIGCHLD handler: %s", strerror(errno));
154    }
155}
156
157/*
158 * Set the SIGCHLD handler back to default behavior in zygote children
159 */
160static void unsetSignalHandler()
161{
162    int err;
163    struct sigaction sa;
164
165    memset(&sa, 0, sizeof(sa));
166
167    sa.sa_handler = SIG_DFL;
168
169    err = sigaction (SIGCHLD, &sa, NULL);
170
171    if (err < 0) {
172        ALOGW("Error unsetting SIGCHLD handler: %s", strerror(errno));
173    }
174}
175
176/*
177 * Calls POSIX setgroups() using the int[] object as an argument.
178 * A NULL argument is tolerated.
179 */
180
181static int setgroupsIntarray(ArrayObject* gidArray)
182{
183    gid_t *gids;
184    u4 i;
185    s4 *contents;
186
187    if (gidArray == NULL) {
188        return 0;
189    }
190
191    /* just in case gid_t and u4 are different... */
192    gids = (gid_t *)alloca(sizeof(gid_t) * gidArray->length);
193    contents = (s4 *)(void *)gidArray->contents;
194
195    for (i = 0 ; i < gidArray->length ; i++) {
196        gids[i] = (gid_t) contents[i];
197    }
198
199    return setgroups((size_t) gidArray->length, gids);
200}
201
202/*
203 * Sets the resource limits via setrlimit(2) for the values in the
204 * two-dimensional array of integers that's passed in. The second dimension
205 * contains a tuple of length 3: (resource, rlim_cur, rlim_max). NULL is
206 * treated as an empty array.
207 *
208 * -1 is returned on error.
209 */
210static int setrlimitsFromArray(ArrayObject* rlimits)
211{
212    u4 i;
213    struct rlimit rlim;
214
215    if (rlimits == NULL) {
216        return 0;
217    }
218
219    memset (&rlim, 0, sizeof(rlim));
220
221    ArrayObject** tuples = (ArrayObject **)(void *)rlimits->contents;
222
223    for (i = 0; i < rlimits->length; i++) {
224        ArrayObject * rlimit_tuple = tuples[i];
225        s4* contents = (s4 *)(void *)rlimit_tuple->contents;
226        int err;
227
228        if (rlimit_tuple->length != 3) {
229            ALOGE("rlimits array must have a second dimension of size 3");
230            return -1;
231        }
232
233        rlim.rlim_cur = contents[1];
234        rlim.rlim_max = contents[2];
235
236        err = setrlimit(contents[0], &rlim);
237
238        if (err < 0) {
239            return -1;
240        }
241    }
242
243    return 0;
244}
245
246/*
247 * Create a private mount namespace and bind mount appropriate emulated
248 * storage for the given user.
249 */
250static int mountEmulatedStorage(uid_t uid, u4 mountMode) {
251    // See storage config details at http://source.android.com/tech/storage/
252    userid_t userid = multiuser_get_user_id(uid);
253
254    // Create a second private mount namespace for our process
255    if (unshare(CLONE_NEWNS) == -1) {
256        ALOGE("Failed to unshare(): %s", strerror(errno));
257        return -1;
258    }
259
260    // Create bind mounts to expose external storage
261    if (mountMode == MOUNT_EXTERNAL_MULTIUSER
262            || mountMode == MOUNT_EXTERNAL_MULTIUSER_ALL) {
263        // These paths must already be created by init.rc
264        const char* source = getenv("EMULATED_STORAGE_SOURCE");
265        const char* target = getenv("EMULATED_STORAGE_TARGET");
266        const char* legacy = getenv("EXTERNAL_STORAGE");
267        if (source == NULL || target == NULL || legacy == NULL) {
268            ALOGE("Storage environment undefined; unable to provide external storage");
269            return -1;
270        }
271
272        // Prepare source paths
273        char source_user[PATH_MAX];
274        char target_user[PATH_MAX];
275
276        // /mnt/shell/emulated/0
277        snprintf(source_user, PATH_MAX, "%s/%d", source, userid);
278        // /storage/emulated/0
279        snprintf(target_user, PATH_MAX, "%s/%d", target, userid);
280
281        if (fs_prepare_dir(source_user, 0000, 0, 0) == -1
282                || fs_prepare_dir(target_user, 0000, 0, 0) == -1) {
283            return -1;
284        }
285
286        if (mountMode == MOUNT_EXTERNAL_MULTIUSER_ALL) {
287            // Mount entire external storage tree for all users
288            if (mount(source, target, NULL, MS_BIND, NULL) == -1) {
289                ALOGE("Failed to mount %s to %s: %s", source, target, strerror(errno));
290                return -1;
291            }
292        } else {
293            // Only mount user-specific external storage
294            if (mount(source_user, target_user, NULL, MS_BIND, NULL) == -1) {
295                ALOGE("Failed to mount %s to %s: %s", source_user, target_user, strerror(errno));
296                return -1;
297            }
298        }
299
300        if (fs_prepare_dir(legacy, 0000, 0, 0) == -1) {
301            return -1;
302        }
303
304        // Finally, mount user-specific path into place for legacy users
305        if (mount(target_user, legacy, NULL, MS_BIND | MS_REC, NULL) == -1) {
306            ALOGE("Failed to mount %s to %s: %s", target_user, legacy, strerror(errno));
307            return -1;
308        }
309
310    } else {
311        ALOGE("Mount mode %d unsupported", mountMode);
312        return -1;
313    }
314
315    return 0;
316}
317
318/* native public static int fork(); */
319static void Dalvik_dalvik_system_Zygote_fork(const u4* args, JValue* pResult)
320{
321    pid_t pid;
322
323    if (!gDvm.zygote) {
324        dvmThrowIllegalStateException(
325            "VM instance not started with -Xzygote");
326
327        RETURN_VOID();
328    }
329
330    if (!dvmGcPreZygoteFork()) {
331        ALOGE("pre-fork heap failed");
332        dvmAbort();
333    }
334
335    setSignalHandler();
336
337    dvmDumpLoaderStats("zygote");
338    pid = fork();
339
340#ifdef HAVE_ANDROID_OS
341    if (pid == 0) {
342        /* child process */
343        extern int gMallocLeakZygoteChild;
344        gMallocLeakZygoteChild = 1;
345    }
346#endif
347
348    RETURN_INT(pid);
349}
350
351/*
352 * Enable/disable debug features requested by the caller.
353 *
354 * debugger
355 *   If set, enable debugging; if not set, disable debugging.  This is
356 *   easy to handle, because the JDWP thread isn't started until we call
357 *   dvmInitAfterZygote().
358 * checkjni
359 *   If set, make sure "check JNI" is enabled.
360 * assert
361 *   If set, make sure assertions are enabled.  This gets fairly weird,
362 *   because it affects the result of a method called by class initializers,
363 *   and hence can't affect pre-loaded/initialized classes.
364 * safemode
365 *   If set, operates the VM in the safe mode. The definition of "safe mode" is
366 *   implementation dependent and currently only the JIT compiler is disabled.
367 *   This is easy to handle because the compiler thread and associated resources
368 *   are not requested until we call dvmInitAfterZygote().
369 */
370static void enableDebugFeatures(u4 debugFlags)
371{
372    ALOGV("debugFlags is 0x%02x", debugFlags);
373
374    gDvm.jdwpAllowed = ((debugFlags & DEBUG_ENABLE_DEBUGGER) != 0);
375
376    if ((debugFlags & DEBUG_ENABLE_CHECKJNI) != 0) {
377        /* turn it on if it's not already enabled */
378        dvmLateEnableCheckedJni();
379    }
380
381    if ((debugFlags & DEBUG_ENABLE_JNI_LOGGING) != 0) {
382        gDvmJni.logThirdPartyJni = true;
383    }
384
385    if ((debugFlags & DEBUG_ENABLE_ASSERT) != 0) {
386        /* turn it on if it's not already enabled */
387        dvmLateEnableAssertions();
388    }
389
390    if ((debugFlags & DEBUG_ENABLE_SAFEMODE) != 0) {
391#if defined(WITH_JIT)
392        /* turn off the jit if it is explicitly requested by the app */
393        if (gDvm.executionMode == kExecutionModeJit)
394            gDvm.executionMode = kExecutionModeInterpFast;
395#endif
396    }
397
398#ifdef HAVE_ANDROID_OS
399    if ((debugFlags & DEBUG_ENABLE_DEBUGGER) != 0) {
400        /* To let a non-privileged gdbserver attach to this
401         * process, we must set its dumpable bit flag. However
402         * we are not interested in generating a coredump in
403         * case of a crash, so also set the coredump size to 0
404         * to disable that
405         */
406        if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) {
407            ALOGE("could not set dumpable bit flag for pid %d: %s",
408                 getpid(), strerror(errno));
409        } else {
410            struct rlimit rl;
411            rl.rlim_cur = 0;
412            rl.rlim_max = RLIM_INFINITY;
413            if (setrlimit(RLIMIT_CORE, &rl) < 0) {
414                ALOGE("could not disable core file generation for pid %d: %s",
415                    getpid(), strerror(errno));
416            }
417        }
418    }
419#endif
420}
421
422/*
423 * Set Linux capability flags.
424 *
425 * Returns 0 on success, errno on failure.
426 */
427static int setCapabilities(int64_t permitted, int64_t effective)
428{
429#ifdef HAVE_ANDROID_OS
430    struct __user_cap_header_struct capheader;
431    struct __user_cap_data_struct capdata;
432
433    memset(&capheader, 0, sizeof(capheader));
434    memset(&capdata, 0, sizeof(capdata));
435
436    capheader.version = _LINUX_CAPABILITY_VERSION;
437    capheader.pid = 0;
438
439    capdata.effective = effective;
440    capdata.permitted = permitted;
441
442    ALOGV("CAPSET perm=%llx eff=%llx", permitted, effective);
443    if (capset(&capheader, &capdata) != 0)
444        return errno;
445#endif /*HAVE_ANDROID_OS*/
446
447    return 0;
448}
449
450/*
451 * Set SELinux security context.
452 *
453 * Returns 0 on success, -1 on failure.
454 */
455static int setSELinuxContext(uid_t uid, bool isSystemServer,
456                             const char *seInfo, const char *niceName)
457{
458#ifdef HAVE_ANDROID_OS
459    return selinux_android_setcontext(uid, isSystemServer, seInfo, niceName);
460#else
461    return 0;
462#endif
463}
464
465static bool needsNoRandomizeWorkaround() {
466#if !defined(__arm__)
467    return false;
468#else
469    int major;
470    int minor;
471    struct utsname uts;
472    if (uname(&uts) == -1) {
473        return false;
474    }
475
476    if (sscanf(uts.release, "%d.%d", &major, &minor) != 2) {
477        return false;
478    }
479
480    // Kernels before 3.4.* need the workaround.
481    return (major < 3) || ((major == 3) && (minor < 4));
482#endif
483}
484
485/*
486 * Utility routine to fork zygote and specialize the child process.
487 */
488static pid_t forkAndSpecializeCommon(const u4* args, bool isSystemServer)
489{
490    pid_t pid;
491
492    uid_t uid = (uid_t) args[0];
493    gid_t gid = (gid_t) args[1];
494    ArrayObject* gids = (ArrayObject *)args[2];
495    u4 debugFlags = args[3];
496    ArrayObject *rlimits = (ArrayObject *)args[4];
497    u4 mountMode = MOUNT_EXTERNAL_NONE;
498    int64_t permittedCapabilities, effectiveCapabilities;
499    char *seInfo = NULL;
500    char *niceName = NULL;
501
502    if (isSystemServer) {
503        /*
504         * Don't use GET_ARG_LONG here for now.  gcc is generating code
505         * that uses register d8 as a temporary, and that's coming out
506         * scrambled in the child process.  b/3138621
507         */
508        //permittedCapabilities = GET_ARG_LONG(args, 5);
509        //effectiveCapabilities = GET_ARG_LONG(args, 7);
510        permittedCapabilities = args[5] | (int64_t) args[6] << 32;
511        effectiveCapabilities = args[7] | (int64_t) args[8] << 32;
512    } else {
513        mountMode = args[5];
514        permittedCapabilities = effectiveCapabilities = 0;
515        StringObject* seInfoObj = (StringObject*)args[6];
516        if (seInfoObj) {
517            seInfo = dvmCreateCstrFromString(seInfoObj);
518            if (!seInfo) {
519                ALOGE("seInfo dvmCreateCstrFromString failed");
520                dvmAbort();
521            }
522        }
523        StringObject* niceNameObj = (StringObject*)args[7];
524        if (niceNameObj) {
525            niceName = dvmCreateCstrFromString(niceNameObj);
526            if (!niceName) {
527                ALOGE("niceName dvmCreateCstrFromString failed");
528                dvmAbort();
529            }
530        }
531    }
532
533    if (!gDvm.zygote) {
534        dvmThrowIllegalStateException(
535            "VM instance not started with -Xzygote");
536
537        return -1;
538    }
539
540    if (!dvmGcPreZygoteFork()) {
541        ALOGE("pre-fork heap failed");
542        dvmAbort();
543    }
544
545    setSignalHandler();
546
547    dvmDumpLoaderStats("zygote");
548    pid = fork();
549
550    if (pid == 0) {
551        int err;
552        /* The child process */
553
554#ifdef HAVE_ANDROID_OS
555        extern int gMallocLeakZygoteChild;
556        gMallocLeakZygoteChild = 1;
557
558        /* keep caps across UID change, unless we're staying root */
559        if (uid != 0) {
560            err = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
561
562            if (err < 0) {
563                ALOGE("cannot PR_SET_KEEPCAPS: %s", strerror(errno));
564                dvmAbort();
565            }
566        }
567
568        for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {
569            err = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
570            if (err < 0) {
571                if (errno == EINVAL) {
572                    ALOGW("PR_CAPBSET_DROP %d failed: %s. "
573                          "Please make sure your kernel is compiled with "
574                          "file capabilities support enabled.",
575                          i, strerror(errno));
576                } else {
577                    ALOGE("PR_CAPBSET_DROP %d failed: %s.", i, strerror(errno));
578                    dvmAbort();
579                }
580            }
581        }
582
583#endif /* HAVE_ANDROID_OS */
584
585        if (mountMode != MOUNT_EXTERNAL_NONE) {
586            err = mountEmulatedStorage(uid, mountMode);
587            if (err < 0) {
588                ALOGE("cannot mountExternalStorage(): %s", strerror(errno));
589
590                if (errno == ENOTCONN || errno == EROFS) {
591                    // When device is actively encrypting, we get ENOTCONN here
592                    // since FUSE was mounted before the framework restarted.
593                    // When encrypted device is booting, we get EROFS since
594                    // FUSE hasn't been created yet by init.
595                    // In either case, continue without external storage.
596                } else {
597                    dvmAbort();
598                }
599            }
600        }
601
602        err = setgroupsIntarray(gids);
603        if (err < 0) {
604            ALOGE("cannot setgroups(): %s", strerror(errno));
605            dvmAbort();
606        }
607
608        err = setrlimitsFromArray(rlimits);
609        if (err < 0) {
610            ALOGE("cannot setrlimit(): %s", strerror(errno));
611            dvmAbort();
612        }
613
614        err = setresgid(gid, gid, gid);
615        if (err < 0) {
616            ALOGE("cannot setresgid(%d): %s", gid, strerror(errno));
617            dvmAbort();
618        }
619
620        err = setresuid(uid, uid, uid);
621        if (err < 0) {
622            ALOGE("cannot setresuid(%d): %s", uid, strerror(errno));
623            dvmAbort();
624        }
625
626        if (needsNoRandomizeWorkaround()) {
627            int current = personality(0xffffFFFF);
628            int success = personality((ADDR_NO_RANDOMIZE | current));
629            if (success == -1) {
630                ALOGW("Personality switch failed. current=%d error=%d\n", current, errno);
631            }
632        }
633
634        err = setCapabilities(permittedCapabilities, effectiveCapabilities);
635        if (err != 0) {
636            ALOGE("cannot set capabilities (%llx,%llx): %s",
637                permittedCapabilities, effectiveCapabilities, strerror(err));
638            dvmAbort();
639        }
640
641        err = set_sched_policy(0, SP_DEFAULT);
642        if (err < 0) {
643            ALOGE("cannot set_sched_policy(0, SP_DEFAULT): %s", strerror(-err));
644            dvmAbort();
645        }
646
647        err = setSELinuxContext(uid, isSystemServer, seInfo, niceName);
648        if (err < 0) {
649            ALOGE("cannot set SELinux context: %s\n", strerror(errno));
650            dvmAbort();
651        }
652        // These free(3) calls are safe because we know we're only ever forking
653        // a single-threaded process, so we know no other thread held the heap
654        // lock when we forked.
655        free(seInfo);
656        free(niceName);
657
658        /*
659         * Our system thread ID has changed.  Get the new one.
660         */
661        Thread* thread = dvmThreadSelf();
662        thread->systemTid = dvmGetSysThreadId();
663
664        /* configure additional debug options */
665        enableDebugFeatures(debugFlags);
666
667        unsetSignalHandler();
668        gDvm.zygote = false;
669        if (!dvmInitAfterZygote()) {
670            ALOGE("error in post-zygote initialization");
671            dvmAbort();
672        }
673    } else if (pid > 0) {
674        /* the parent process */
675        free(seInfo);
676        free(niceName);
677    }
678
679    return pid;
680}
681
682/*
683 * native public static int nativeForkAndSpecialize(int uid, int gid,
684 *     int[] gids, int debugFlags, int[][] rlimits, int mountExternal,
685 *     String seInfo, String niceName);
686 */
687static void Dalvik_dalvik_system_Zygote_forkAndSpecialize(const u4* args,
688    JValue* pResult)
689{
690    pid_t pid;
691
692    pid = forkAndSpecializeCommon(args, false);
693
694    RETURN_INT(pid);
695}
696
697/*
698 * native public static int nativeForkSystemServer(int uid, int gid,
699 *     int[] gids, int debugFlags, int[][] rlimits,
700 *     long permittedCapabilities, long effectiveCapabilities);
701 */
702static void Dalvik_dalvik_system_Zygote_forkSystemServer(
703        const u4* args, JValue* pResult)
704{
705    pid_t pid;
706    pid = forkAndSpecializeCommon(args, true);
707
708    /* The zygote process checks whether the child process has died or not. */
709    if (pid > 0) {
710        int status;
711
712        ALOGI("System server process %d has been created", pid);
713        gDvm.systemServerPid = pid;
714        /* There is a slight window that the system server process has crashed
715         * but it went unnoticed because we haven't published its pid yet. So
716         * we recheck here just to make sure that all is well.
717         */
718        if (waitpid(pid, &status, WNOHANG) == pid) {
719            ALOGE("System server process %d has died. Restarting Zygote!", pid);
720            kill(getpid(), SIGKILL);
721        }
722    }
723    RETURN_INT(pid);
724}
725
726const DalvikNativeMethod dvm_dalvik_system_Zygote[] = {
727    { "nativeFork", "()I",
728      Dalvik_dalvik_system_Zygote_fork },
729    { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;)I",
730      Dalvik_dalvik_system_Zygote_forkAndSpecialize },
731    { "nativeForkSystemServer", "(II[II[[IJJ)I",
732      Dalvik_dalvik_system_Zygote_forkSystemServer },
733    { NULL, NULL, NULL },
734};
735