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