ZygoteProcess.java revision 8faeab8735f1a5759b24583d55853a488639546b
1/* 2 * Copyright (C) 2016 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 android.os; 18 19import android.net.LocalSocket; 20import android.net.LocalSocketAddress; 21import android.util.Log; 22import android.util.Slog; 23 24import com.android.internal.annotations.GuardedBy; 25import com.android.internal.os.Zygote; 26import com.android.internal.util.Preconditions; 27 28import java.io.BufferedWriter; 29import java.io.DataInputStream; 30import java.io.IOException; 31import java.io.OutputStreamWriter; 32import java.nio.charset.StandardCharsets; 33import java.util.ArrayList; 34import java.util.Arrays; 35import java.util.Collections; 36import java.util.List; 37import java.util.UUID; 38 39/*package*/ class ZygoteStartFailedEx extends Exception { 40 ZygoteStartFailedEx(String s) { 41 super(s); 42 } 43 44 ZygoteStartFailedEx(Throwable cause) { 45 super(cause); 46 } 47 48 ZygoteStartFailedEx(String s, Throwable cause) { 49 super(s, cause); 50 } 51} 52 53/** 54 * Maintains communication state with the zygote processes. This class is responsible 55 * for the sockets opened to the zygotes and for starting processes on behalf of the 56 * {@link android.os.Process} class. 57 * 58 * {@hide} 59 */ 60public class ZygoteProcess { 61 private static final String LOG_TAG = "ZygoteProcess"; 62 63 /** 64 * The name of the socket used to communicate with the primary zygote. 65 */ 66 private final LocalSocketAddress mSocket; 67 68 /** 69 * The name of the secondary (alternate ABI) zygote socket. 70 */ 71 private final LocalSocketAddress mSecondarySocket; 72 73 public ZygoteProcess(String primarySocket, String secondarySocket) { 74 this(new LocalSocketAddress(primarySocket, LocalSocketAddress.Namespace.RESERVED), 75 new LocalSocketAddress(secondarySocket, LocalSocketAddress.Namespace.RESERVED)); 76 } 77 78 public ZygoteProcess(LocalSocketAddress primarySocket, LocalSocketAddress secondarySocket) { 79 mSocket = primarySocket; 80 mSecondarySocket = secondarySocket; 81 } 82 83 public LocalSocketAddress getPrimarySocketAddress() { 84 return mSocket; 85 } 86 87 /** 88 * State for communicating with the zygote process. 89 */ 90 public static class ZygoteState { 91 final LocalSocket socket; 92 final DataInputStream inputStream; 93 final BufferedWriter writer; 94 final List<String> abiList; 95 96 boolean mClosed; 97 98 private ZygoteState(LocalSocket socket, DataInputStream inputStream, 99 BufferedWriter writer, List<String> abiList) { 100 this.socket = socket; 101 this.inputStream = inputStream; 102 this.writer = writer; 103 this.abiList = abiList; 104 } 105 106 public static ZygoteState connect(LocalSocketAddress address) throws IOException { 107 DataInputStream zygoteInputStream = null; 108 BufferedWriter zygoteWriter = null; 109 final LocalSocket zygoteSocket = new LocalSocket(); 110 111 try { 112 zygoteSocket.connect(address); 113 114 zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream()); 115 116 zygoteWriter = new BufferedWriter(new OutputStreamWriter( 117 zygoteSocket.getOutputStream()), 256); 118 } catch (IOException ex) { 119 try { 120 zygoteSocket.close(); 121 } catch (IOException ignore) { 122 } 123 124 throw ex; 125 } 126 127 String abiListString = getAbiList(zygoteWriter, zygoteInputStream); 128 Log.i("Zygote", "Process: zygote socket " + address.getNamespace() + "/" 129 + address.getName() + " opened, supported ABIS: " + abiListString); 130 131 return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter, 132 Arrays.asList(abiListString.split(","))); 133 } 134 135 boolean matches(String abi) { 136 return abiList.contains(abi); 137 } 138 139 public void close() { 140 try { 141 socket.close(); 142 } catch (IOException ex) { 143 Log.e(LOG_TAG,"I/O exception on routine close", ex); 144 } 145 146 mClosed = true; 147 } 148 149 boolean isClosed() { 150 return mClosed; 151 } 152 } 153 154 /** 155 * Lock object to protect access to the two ZygoteStates below. This lock must be 156 * acquired while communicating over the ZygoteState's socket, to prevent 157 * interleaved access. 158 */ 159 private final Object mLock = new Object(); 160 161 /** 162 * List of exemptions to the API blacklist. These are prefix matches on the runtime format 163 * symbol signature. Any matching symbol is treated by the runtime as being on the light grey 164 * list. 165 */ 166 private List<String> mApiBlacklistExemptions = Collections.emptyList(); 167 168 /** 169 * The state of the connection to the primary zygote. 170 */ 171 private ZygoteState primaryZygoteState; 172 173 /** 174 * The state of the connection to the secondary zygote. 175 */ 176 private ZygoteState secondaryZygoteState; 177 178 /** 179 * Start a new process. 180 * 181 * <p>If processes are enabled, a new process is created and the 182 * static main() function of a <var>processClass</var> is executed there. 183 * The process will continue running after this function returns. 184 * 185 * <p>If processes are not enabled, a new thread in the caller's 186 * process is created and main() of <var>processclass</var> called there. 187 * 188 * <p>The niceName parameter, if not an empty string, is a custom name to 189 * give to the process instead of using processClass. This allows you to 190 * make easily identifyable processes even if you are using the same base 191 * <var>processClass</var> to start them. 192 * 193 * When invokeWith is not null, the process will be started as a fresh app 194 * and not a zygote fork. Note that this is only allowed for uid 0 or when 195 * runtimeFlags contains DEBUG_ENABLE_DEBUGGER. 196 * 197 * @param processClass The class to use as the process's main entry 198 * point. 199 * @param niceName A more readable name to use for the process. 200 * @param uid The user-id under which the process will run. 201 * @param gid The group-id under which the process will run. 202 * @param gids Additional group-ids associated with the process. 203 * @param runtimeFlags Additional flags. 204 * @param targetSdkVersion The target SDK version for the app. 205 * @param seInfo null-ok SELinux information for the new process. 206 * @param abi non-null the ABI this app should be started with. 207 * @param instructionSet null-ok the instruction set to use. 208 * @param appDataDir null-ok the data directory of the app. 209 * @param invokeWith null-ok the command to invoke with. 210 * @param zygoteArgs Additional arguments to supply to the zygote process. 211 * 212 * @return An object that describes the result of the attempt to start the process. 213 * @throws RuntimeException on fatal start failure 214 */ 215 public final Process.ProcessStartResult start(final String processClass, 216 final String niceName, 217 int uid, int gid, int[] gids, 218 int runtimeFlags, int mountExternal, 219 int targetSdkVersion, 220 String seInfo, 221 String abi, 222 String instructionSet, 223 String appDataDir, 224 String invokeWith, 225 String[] zygoteArgs) { 226 try { 227 return startViaZygote(processClass, niceName, uid, gid, gids, 228 runtimeFlags, mountExternal, targetSdkVersion, seInfo, 229 abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */, 230 zygoteArgs); 231 } catch (ZygoteStartFailedEx ex) { 232 Log.e(LOG_TAG, 233 "Starting VM process through Zygote failed"); 234 throw new RuntimeException( 235 "Starting VM process through Zygote failed", ex); 236 } 237 } 238 239 /** retry interval for opening a zygote socket */ 240 static final int ZYGOTE_RETRY_MILLIS = 500; 241 242 /** 243 * Queries the zygote for the list of ABIS it supports. 244 * 245 * @throws ZygoteStartFailedEx if the query failed. 246 */ 247 @GuardedBy("mLock") 248 private static String getAbiList(BufferedWriter writer, DataInputStream inputStream) 249 throws IOException { 250 // Each query starts with the argument count (1 in this case) 251 writer.write("1"); 252 // ... followed by a new-line. 253 writer.newLine(); 254 // ... followed by our only argument. 255 writer.write("--query-abi-list"); 256 writer.newLine(); 257 writer.flush(); 258 259 // The response is a length prefixed stream of ASCII bytes. 260 int numBytes = inputStream.readInt(); 261 byte[] bytes = new byte[numBytes]; 262 inputStream.readFully(bytes); 263 264 return new String(bytes, StandardCharsets.US_ASCII); 265 } 266 267 /** 268 * Sends an argument list to the zygote process, which starts a new child 269 * and returns the child's pid. Please note: the present implementation 270 * replaces newlines in the argument list with spaces. 271 * 272 * @throws ZygoteStartFailedEx if process start failed for any reason 273 */ 274 @GuardedBy("mLock") 275 private static Process.ProcessStartResult zygoteSendArgsAndGetResult( 276 ZygoteState zygoteState, ArrayList<String> args) 277 throws ZygoteStartFailedEx { 278 try { 279 // Throw early if any of the arguments are malformed. This means we can 280 // avoid writing a partial response to the zygote. 281 int sz = args.size(); 282 for (int i = 0; i < sz; i++) { 283 if (args.get(i).indexOf('\n') >= 0) { 284 throw new ZygoteStartFailedEx("embedded newlines not allowed"); 285 } 286 } 287 288 /** 289 * See com.android.internal.os.SystemZygoteInit.readArgumentList() 290 * Presently the wire format to the zygote process is: 291 * a) a count of arguments (argc, in essence) 292 * b) a number of newline-separated argument strings equal to count 293 * 294 * After the zygote process reads these it will write the pid of 295 * the child or -1 on failure, followed by boolean to 296 * indicate whether a wrapper process was used. 297 */ 298 final BufferedWriter writer = zygoteState.writer; 299 final DataInputStream inputStream = zygoteState.inputStream; 300 301 writer.write(Integer.toString(args.size())); 302 writer.newLine(); 303 304 for (int i = 0; i < sz; i++) { 305 String arg = args.get(i); 306 writer.write(arg); 307 writer.newLine(); 308 } 309 310 writer.flush(); 311 312 // Should there be a timeout on this? 313 Process.ProcessStartResult result = new Process.ProcessStartResult(); 314 315 // Always read the entire result from the input stream to avoid leaving 316 // bytes in the stream for future process starts to accidentally stumble 317 // upon. 318 result.pid = inputStream.readInt(); 319 result.usingWrapper = inputStream.readBoolean(); 320 321 if (result.pid < 0) { 322 throw new ZygoteStartFailedEx("fork() failed"); 323 } 324 return result; 325 } catch (IOException ex) { 326 zygoteState.close(); 327 throw new ZygoteStartFailedEx(ex); 328 } 329 } 330 331 /** 332 * Starts a new process via the zygote mechanism. 333 * 334 * @param processClass Class name whose static main() to run 335 * @param niceName 'nice' process name to appear in ps 336 * @param uid a POSIX uid that the new process should setuid() to 337 * @param gid a POSIX gid that the new process shuold setgid() to 338 * @param gids null-ok; a list of supplementary group IDs that the 339 * new process should setgroup() to. 340 * @param runtimeFlags Additional flags for the runtime. 341 * @param targetSdkVersion The target SDK version for the app. 342 * @param seInfo null-ok SELinux information for the new process. 343 * @param abi the ABI the process should use. 344 * @param instructionSet null-ok the instruction set to use. 345 * @param appDataDir null-ok the data directory of the app. 346 * @param startChildZygote Start a sub-zygote. This creates a new zygote process 347 * that has its state cloned from this zygote process. 348 * @param extraArgs Additional arguments to supply to the zygote process. 349 * @return An object that describes the result of the attempt to start the process. 350 * @throws ZygoteStartFailedEx if process start failed for any reason 351 */ 352 private Process.ProcessStartResult startViaZygote(final String processClass, 353 final String niceName, 354 final int uid, final int gid, 355 final int[] gids, 356 int runtimeFlags, int mountExternal, 357 int targetSdkVersion, 358 String seInfo, 359 String abi, 360 String instructionSet, 361 String appDataDir, 362 String invokeWith, 363 boolean startChildZygote, 364 String[] extraArgs) 365 throws ZygoteStartFailedEx { 366 ArrayList<String> argsForZygote = new ArrayList<String>(); 367 368 // --runtime-args, --setuid=, --setgid=, 369 // and --setgroups= must go first 370 argsForZygote.add("--runtime-args"); 371 argsForZygote.add("--setuid=" + uid); 372 argsForZygote.add("--setgid=" + gid); 373 argsForZygote.add("--runtime-flags=" + runtimeFlags); 374 if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) { 375 argsForZygote.add("--mount-external-default"); 376 } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) { 377 argsForZygote.add("--mount-external-read"); 378 } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) { 379 argsForZygote.add("--mount-external-write"); 380 } 381 argsForZygote.add("--target-sdk-version=" + targetSdkVersion); 382 383 // --setgroups is a comma-separated list 384 if (gids != null && gids.length > 0) { 385 StringBuilder sb = new StringBuilder(); 386 sb.append("--setgroups="); 387 388 int sz = gids.length; 389 for (int i = 0; i < sz; i++) { 390 if (i != 0) { 391 sb.append(','); 392 } 393 sb.append(gids[i]); 394 } 395 396 argsForZygote.add(sb.toString()); 397 } 398 399 if (niceName != null) { 400 argsForZygote.add("--nice-name=" + niceName); 401 } 402 403 if (seInfo != null) { 404 argsForZygote.add("--seinfo=" + seInfo); 405 } 406 407 if (instructionSet != null) { 408 argsForZygote.add("--instruction-set=" + instructionSet); 409 } 410 411 if (appDataDir != null) { 412 argsForZygote.add("--app-data-dir=" + appDataDir); 413 } 414 415 if (invokeWith != null) { 416 argsForZygote.add("--invoke-with"); 417 argsForZygote.add(invokeWith); 418 } 419 420 if (startChildZygote) { 421 argsForZygote.add("--start-child-zygote"); 422 } 423 424 argsForZygote.add(processClass); 425 426 if (extraArgs != null) { 427 for (String arg : extraArgs) { 428 argsForZygote.add(arg); 429 } 430 } 431 432 synchronized(mLock) { 433 return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote); 434 } 435 } 436 437 /** 438 * Closes the connections to the zygote, if they exist. 439 */ 440 public void close() { 441 if (primaryZygoteState != null) { 442 primaryZygoteState.close(); 443 } 444 if (secondaryZygoteState != null) { 445 secondaryZygoteState.close(); 446 } 447 } 448 449 /** 450 * Tries to establish a connection to the zygote that handles a given {@code abi}. Might block 451 * and retry if the zygote is unresponsive. This method is a no-op if a connection is 452 * already open. 453 */ 454 public void establishZygoteConnectionForAbi(String abi) { 455 try { 456 synchronized(mLock) { 457 openZygoteSocketIfNeeded(abi); 458 } 459 } catch (ZygoteStartFailedEx ex) { 460 throw new RuntimeException("Unable to connect to zygote for abi: " + abi, ex); 461 } 462 } 463 464 /** 465 * Push hidden API blacklisting exemptions into the zygote process(es). 466 * 467 * <p>The list of exemptions will take affect for all new processes forked from the zygote after 468 * this call. 469 * 470 * @param exemptions List of hidden API exemption prefixes. 471 */ 472 public void setApiBlacklistExemptions(List<String> exemptions) { 473 synchronized (mLock) { 474 mApiBlacklistExemptions = exemptions; 475 maybeSetApiBlacklistExemptions(primaryZygoteState, true); 476 maybeSetApiBlacklistExemptions(secondaryZygoteState, true); 477 } 478 } 479 480 @GuardedBy("mLock") 481 private void maybeSetApiBlacklistExemptions(ZygoteState state, boolean sendIfEmpty) { 482 if (state == null || state.isClosed()) { 483 return; 484 } 485 if (!sendIfEmpty && mApiBlacklistExemptions.isEmpty()) { 486 return; 487 } 488 try { 489 state.writer.write(Integer.toString(mApiBlacklistExemptions.size() + 1)); 490 state.writer.newLine(); 491 state.writer.write("--set-api-blacklist-exemptions"); 492 state.writer.newLine(); 493 for (int i = 0; i < mApiBlacklistExemptions.size(); ++i) { 494 state.writer.write(mApiBlacklistExemptions.get(i)); 495 state.writer.newLine(); 496 } 497 state.writer.flush(); 498 int status = state.inputStream.readInt(); 499 if (status != 0) { 500 Slog.e(LOG_TAG, "Failed to set API blacklist exemptions; status " + status); 501 } 502 } catch (IOException ioe) { 503 Slog.e(LOG_TAG, "Failed to set API blacklist exemptions", ioe); 504 } 505 } 506 507 /** 508 * Tries to open socket to Zygote process if not already open. If 509 * already open, does nothing. May block and retry. Requires that mLock be held. 510 */ 511 @GuardedBy("mLock") 512 private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx { 513 Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held"); 514 515 if (primaryZygoteState == null || primaryZygoteState.isClosed()) { 516 try { 517 primaryZygoteState = ZygoteState.connect(mSocket); 518 } catch (IOException ioe) { 519 throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe); 520 } 521 maybeSetApiBlacklistExemptions(primaryZygoteState, false); 522 } 523 if (primaryZygoteState.matches(abi)) { 524 return primaryZygoteState; 525 } 526 527 // The primary zygote didn't match. Try the secondary. 528 if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) { 529 try { 530 secondaryZygoteState = ZygoteState.connect(mSecondarySocket); 531 } catch (IOException ioe) { 532 throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe); 533 } 534 maybeSetApiBlacklistExemptions(secondaryZygoteState, false); 535 } 536 537 if (secondaryZygoteState.matches(abi)) { 538 return secondaryZygoteState; 539 } 540 541 throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi); 542 } 543 544 /** 545 * Instructs the zygote to pre-load the classes and native libraries at the given paths 546 * for the specified abi. Not all zygotes support this function. 547 */ 548 public boolean preloadPackageForAbi(String packagePath, String libsPath, String libFileName, 549 String cacheKey, String abi) throws ZygoteStartFailedEx, 550 IOException { 551 synchronized(mLock) { 552 ZygoteState state = openZygoteSocketIfNeeded(abi); 553 state.writer.write("5"); 554 state.writer.newLine(); 555 556 state.writer.write("--preload-package"); 557 state.writer.newLine(); 558 559 state.writer.write(packagePath); 560 state.writer.newLine(); 561 562 state.writer.write(libsPath); 563 state.writer.newLine(); 564 565 state.writer.write(libFileName); 566 state.writer.newLine(); 567 568 state.writer.write(cacheKey); 569 state.writer.newLine(); 570 571 state.writer.flush(); 572 573 return (state.inputStream.readInt() == 0); 574 } 575 } 576 577 /** 578 * Instructs the zygote to preload the default set of classes and resources. Returns 579 * {@code true} if a preload was performed as a result of this call, and {@code false} 580 * otherwise. The latter usually means that the zygote eagerly preloaded at startup 581 * or due to a previous call to {@code preloadDefault}. Note that this call is synchronous. 582 */ 583 public boolean preloadDefault(String abi) throws ZygoteStartFailedEx, IOException { 584 synchronized (mLock) { 585 ZygoteState state = openZygoteSocketIfNeeded(abi); 586 // Each query starts with the argument count (1 in this case) 587 state.writer.write("1"); 588 state.writer.newLine(); 589 state.writer.write("--preload-default"); 590 state.writer.newLine(); 591 state.writer.flush(); 592 593 return (state.inputStream.readInt() == 0); 594 } 595 } 596 597 /** 598 * Try connecting to the Zygote over and over again until we hit a time-out. 599 * @param socketName The name of the socket to connect to. 600 */ 601 public static void waitForConnectionToZygote(String socketName) { 602 final LocalSocketAddress address = 603 new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.RESERVED); 604 waitForConnectionToZygote(address); 605 } 606 607 /** 608 * Try connecting to the Zygote over and over again until we hit a time-out. 609 * @param address The name of the socket to connect to. 610 */ 611 public static void waitForConnectionToZygote(LocalSocketAddress address) { 612 for (int n = 20; n >= 0; n--) { 613 try { 614 final ZygoteState zs = ZygoteState.connect(address); 615 zs.close(); 616 return; 617 } catch (IOException ioe) { 618 Log.w(LOG_TAG, 619 "Got error connecting to zygote, retrying. msg= " + ioe.getMessage()); 620 } 621 622 try { 623 Thread.sleep(1000); 624 } catch (InterruptedException ie) { 625 } 626 } 627 Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + address.getName()); 628 } 629 630 /** 631 * Starts a new zygote process as a child of this zygote. This is used to create 632 * secondary zygotes that inherit data from the zygote that this object 633 * communicates with. This returns a new ZygoteProcess representing a connection 634 * to the newly created zygote. Throws an exception if the zygote cannot be started. 635 */ 636 public ChildZygoteProcess startChildZygote(final String processClass, 637 final String niceName, 638 int uid, int gid, int[] gids, 639 int runtimeFlags, 640 String seInfo, 641 String abi, 642 String instructionSet) { 643 // Create an unguessable address in the global abstract namespace. 644 final LocalSocketAddress serverAddress = new LocalSocketAddress( 645 processClass + "/" + UUID.randomUUID().toString()); 646 647 final String[] extraArgs = {Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + serverAddress.getName()}; 648 649 Process.ProcessStartResult result; 650 try { 651 result = startViaZygote(processClass, niceName, uid, gid, 652 gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo, 653 abi, instructionSet, null /* appDataDir */, null /* invokeWith */, 654 true /* startChildZygote */, extraArgs); 655 } catch (ZygoteStartFailedEx ex) { 656 throw new RuntimeException("Starting child-zygote through Zygote failed", ex); 657 } 658 659 return new ChildZygoteProcess(serverAddress, result.pid); 660 } 661} 662