Merge changes Id931d441,I83faf974

* changes:
  Zygote: Improve logging and error handling during connections.
  Zygote: Fix race condition on package preloads.
This commit is contained in:
Treehugger Robot
2017-09-14 23:41:34 +00:00
committed by Gerrit Code Review
8 changed files with 305 additions and 272 deletions

View File

@@ -463,7 +463,7 @@ public class ZygoteProcess {
* Instructs the zygote to pre-load the classes and native libraries at the given paths * Instructs the zygote to pre-load the classes and native libraries at the given paths
* for the specified abi. Not all zygotes support this function. * for the specified abi. Not all zygotes support this function.
*/ */
public void preloadPackageForAbi(String packagePath, String libsPath, String cacheKey, public boolean preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
String abi) throws ZygoteStartFailedEx, IOException { String abi) throws ZygoteStartFailedEx, IOException {
synchronized(mLock) { synchronized(mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi); ZygoteState state = openZygoteSocketIfNeeded(abi);
@@ -483,6 +483,8 @@ public class ZygoteProcess {
state.writer.newLine(); state.writer.newLine();
state.writer.flush(); state.writer.flush();
return (state.inputStream.readInt() == 0);
} }
} }

View File

@@ -31,6 +31,7 @@ import android.util.Slog;
import com.android.internal.logging.AndroidConfig; import com.android.internal.logging.AndroidConfig;
import com.android.server.NetworkManagementSocketTagger; import com.android.server.NetworkManagementSocketTagger;
import dalvik.system.VMRuntime; import dalvik.system.VMRuntime;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.TimeZone; import java.util.TimeZone;
@@ -228,8 +229,8 @@ public class RuntimeInit {
* @param argv Argument vector for main() * @param argv Argument vector for main()
* @param classLoader the classLoader to load {@className} with * @param classLoader the classLoader to load {@className} with
*/ */
private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader) private static Runnable findStaticMain(String className, String[] argv,
throws Zygote.MethodAndArgsCaller { ClassLoader classLoader) {
Class<?> cl; Class<?> cl;
try { try {
@@ -263,7 +264,7 @@ public class RuntimeInit {
* clears up all the stack frames that were required in setting * clears up all the stack frames that were required in setting
* up the process. * up the process.
*/ */
throw new Zygote.MethodAndArgsCaller(m, argv); return new MethodAndArgsCaller(m, argv);
} }
public static final void main(String[] argv) { public static final void main(String[] argv) {
@@ -286,8 +287,8 @@ public class RuntimeInit {
if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!"); if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
} }
protected static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
throws Zygote.MethodAndArgsCaller { ClassLoader classLoader) {
// If the application calls System.exit(), terminate the process // If the application calls System.exit(), terminate the process
// immediately without running any shutdown hooks. It is not possible to // immediately without running any shutdown hooks. It is not possible to
// shutdown an Android application gracefully. Among other things, the // shutdown an Android application gracefully. Among other things, the
@@ -300,20 +301,13 @@ public class RuntimeInit {
VMRuntime.getRuntime().setTargetHeapUtilization(0.75f); VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion); VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
final Arguments args; final Arguments args = new Arguments(argv);
try {
args = new Arguments(argv);
} catch (IllegalArgumentException ex) {
Slog.e(TAG, ex.getMessage());
// let the process exit
return;
}
// The end of of the RuntimeInit event (see #zygoteInit). // The end of of the RuntimeInit event (see #zygoteInit).
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// Remaining arguments are passed to the start class's static main // Remaining arguments are passed to the start class's static main
invokeStaticMain(args.startClass, args.startArgs, classLoader); return findStaticMain(args.startClass, args.startArgs, classLoader);
} }
/** /**
@@ -422,4 +416,37 @@ public class RuntimeInit {
System.arraycopy(args, curArg, startArgs, 0, startArgs.length); System.arraycopy(args, curArg, startArgs, 0, startArgs.length);
} }
} }
/**
* Helper class which holds a method and arguments and can call them. This is used as part of
* a trampoline to get rid of the initial process setup stack frames.
*/
static class MethodAndArgsCaller implements Runnable {
/** method to call */
private final Method mMethod;
/** argument array */
private final String[] mArgs;
public MethodAndArgsCaller(Method method, String[] args) {
mMethod = method;
mArgs = args;
}
public void run() {
try {
mMethod.invoke(null, new Object[] { mArgs });
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InvocationTargetException ex) {
Throwable cause = ex.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException(ex);
}
}
}
} }

View File

@@ -26,6 +26,7 @@ import android.util.Log;
import android.webkit.WebViewFactory; import android.webkit.WebViewFactory;
import android.webkit.WebViewFactoryProvider; import android.webkit.WebViewFactoryProvider;
import java.io.DataOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@@ -68,8 +69,7 @@ class WebViewZygoteInit {
} }
@Override @Override
protected boolean handlePreloadPackage(String packagePath, String libsPath, protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
String cacheKey) {
Log.i(TAG, "Beginning package preload"); Log.i(TAG, "Beginning package preload");
// Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that // Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
// our children will reuse the same classloader instead of creating their own. // our children will reuse the same classloader instead of creating their own.
@@ -87,19 +87,28 @@ class WebViewZygoteInit {
// Once we have the classloader, look up the WebViewFactoryProvider implementation and // Once we have the classloader, look up the WebViewFactoryProvider implementation and
// call preloadInZygote() on it to give it the opportunity to preload the native library // call preloadInZygote() on it to give it the opportunity to preload the native library
// and perform any other initialisation work that should be shared among the children. // and perform any other initialisation work that should be shared among the children.
boolean preloadSucceeded = false;
try { try {
Class<WebViewFactoryProvider> providerClass = Class<WebViewFactoryProvider> providerClass =
WebViewFactory.getWebViewProviderClass(loader); WebViewFactory.getWebViewProviderClass(loader);
Object result = providerClass.getMethod("preloadInZygote").invoke(null); Object result = providerClass.getMethod("preloadInZygote").invoke(null);
if (!((Boolean)result).booleanValue()) { preloadSucceeded = ((Boolean) result).booleanValue();
if (!preloadSucceeded) {
Log.e(TAG, "preloadInZygote returned false"); Log.e(TAG, "preloadInZygote returned false");
} }
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException | } catch (ClassNotFoundException | NoSuchMethodException | SecurityException |
IllegalAccessException | InvocationTargetException e) { IllegalAccessException | InvocationTargetException e) {
Log.e(TAG, "Exception while preloading package", e); Log.e(TAG, "Exception while preloading package", e);
} }
try {
DataOutputStream socketOut = getSocketOutputStream();
socketOut.writeInt(preloadSucceeded ? 1 : 0);
} catch (IOException ioe) {
throw new IllegalStateException("Error writing to command socket", ioe);
}
Log.i(TAG, "Package preload done"); Log.i(TAG, "Package preload done");
return false;
} }
} }
@@ -113,16 +122,23 @@ class WebViewZygoteInit {
throw new RuntimeException("Failed to setpgid(0,0)", ex); throw new RuntimeException("Failed to setpgid(0,0)", ex);
} }
final Runnable caller;
try { try {
sServer.registerServerSocket("webview_zygote"); sServer.registerServerSocket("webview_zygote");
sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS)); // The select loop returns early in the child process after a fork and
sServer.closeServerSocket(); // loops forever in the zygote.
} catch (Zygote.MethodAndArgsCaller caller) { caller = sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
caller.run();
} catch (RuntimeException e) { } catch (RuntimeException e) {
Log.e(TAG, "Fatal exception:", e); Log.e(TAG, "Fatal exception:", e);
throw e;
} finally {
sServer.closeServerSocket();
} }
System.exit(0); // We're in the child process and have exited the select loop. Proceed to execute the
// command.
if (caller != null) {
caller.run();
}
} }
} }

View File

@@ -25,7 +25,6 @@ import android.system.StructCapUserData;
import android.system.StructCapUserHeader; import android.system.StructCapUserHeader;
import android.util.BootTimingsTraceLog; import android.util.BootTimingsTraceLog;
import android.util.Slog; import android.util.Slog;
import com.android.internal.os.Zygote.MethodAndArgsCaller;
import dalvik.system.VMRuntime; import dalvik.system.VMRuntime;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.FileDescriptor; import java.io.FileDescriptor;
@@ -61,7 +60,6 @@ public class WrapperInit {
* @param args The command-line arguments. * @param args The command-line arguments.
*/ */
public static void main(String[] args) { public static void main(String[] args) {
try {
// Parse our mandatory arguments. // Parse our mandatory arguments.
int fdNum = Integer.parseInt(args[0], 10); int fdNum = Integer.parseInt(args[0], 10);
int targetSdkVersion = Integer.parseInt(args[1], 10); int targetSdkVersion = Integer.parseInt(args[1], 10);
@@ -88,10 +86,9 @@ public class WrapperInit {
// Launch the application. // Launch the application.
String[] runtimeArgs = new String[args.length - 2]; String[] runtimeArgs = new String[args.length - 2];
System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length); System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length);
WrapperInit.wrapperInit(targetSdkVersion, runtimeArgs); Runnable r = wrapperInit(targetSdkVersion, runtimeArgs);
} catch (Zygote.MethodAndArgsCaller caller) {
caller.run(); r.run();
}
} }
/** /**
@@ -142,8 +139,7 @@ public class WrapperInit {
* @param targetSdkVersion target SDK version * @param targetSdkVersion target SDK version
* @param argv arg strings * @param argv arg strings
*/ */
private static void wrapperInit(int targetSdkVersion, String[] argv) private static Runnable wrapperInit(int targetSdkVersion, String[] argv) {
throws Zygote.MethodAndArgsCaller {
if (RuntimeInit.DEBUG) { if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from wrapper"); Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from wrapper");
} }
@@ -165,7 +161,7 @@ public class WrapperInit {
// Perform the same initialization that would happen after the Zygote forks. // Perform the same initialization that would happen after the Zygote forks.
Zygote.nativePreApplicationInit(); Zygote.nativePreApplicationInit();
RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader); return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
} }
/** /**

View File

@@ -221,39 +221,4 @@ public final class Zygote {
command.append(" '").append(arg.replace("'", "'\\''")).append("'"); command.append(" '").append(arg.replace("'", "'\\''")).append("'");
} }
} }
/**
* Helper exception class which holds a method and arguments and
* can call them. This is used as part of a trampoline to get rid of
* the initial process setup stack frames.
*/
public static class MethodAndArgsCaller extends Exception
implements Runnable {
/** method to call */
private final Method mMethod;
/** argument array */
private final String[] mArgs;
public MethodAndArgsCaller(Method method, String[] args) {
mMethod = method;
mArgs = args;
}
public void run() {
try {
mMethod.invoke(null, new Object[] { mArgs });
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InvocationTargetException ex) {
Throwable cause = ex.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException(ex);
}
}
}
} }

View File

@@ -30,7 +30,6 @@ import android.net.Credentials;
import android.net.LocalSocket; import android.net.LocalSocket;
import android.os.FactoryTest; import android.os.FactoryTest;
import android.os.Process; import android.os.Process;
import android.os.SELinux;
import android.os.SystemProperties; import android.os.SystemProperties;
import android.os.Trace; import android.os.Trace;
import android.system.ErrnoException; import android.system.ErrnoException;
@@ -42,14 +41,13 @@ import java.io.BufferedReader;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.FileOutputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import libcore.io.IoUtils; import libcore.io.IoUtils;
/** /**
@@ -73,6 +71,7 @@ class ZygoteConnection {
private final BufferedReader mSocketReader; private final BufferedReader mSocketReader;
private final Credentials peer; private final Credentials peer;
private final String abiList; private final String abiList;
private boolean isEof;
/** /**
* Constructs instance from connected socket. * Constructs instance from connected socket.
@@ -99,6 +98,8 @@ class ZygoteConnection {
Log.e(TAG, "Cannot read peer credentials", ex); Log.e(TAG, "Cannot read peer credentials", ex);
throw ex; throw ex;
} }
isEof = false;
} }
/** /**
@@ -111,21 +112,14 @@ class ZygoteConnection {
} }
/** /**
* Reads one start command from the command socket. If successful, * Reads one start command from the command socket. If successful, a child is forked and a
* a child is forked and a {@link Zygote.MethodAndArgsCaller} * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child
* exception is thrown in that child while in the parent process, * process. {@code null} is always returned in the parent process (the zygote).
* the method returns normally. On failure, the child is not
* spawned and messages are printed to the log and stderr. Returns
* a boolean status value indicating whether an end-of-file on the command
* socket has been encountered.
* *
* @return false if command socket should continue to be read from, or * If the client closes the socket, an {@code EOF} condition is set, which callers can test
* true if an end-of-file has been encountered. * for by calling {@code ZygoteConnection.isClosedByPeer}.
* @throws Zygote.MethodAndArgsCaller trampoline to invoke main()
* method in child process
*/ */
boolean runOnce(ZygoteServer zygoteServer) throws Zygote.MethodAndArgsCaller { Runnable processOneCommand(ZygoteServer zygoteServer) {
String args[]; String args[];
Arguments parsedArgs = null; Arguments parsedArgs = null;
FileDescriptor[] descriptors; FileDescriptor[] descriptors;
@@ -134,43 +128,36 @@ class ZygoteConnection {
args = readArgumentList(); args = readArgumentList();
descriptors = mSocket.getAncillaryFileDescriptors(); descriptors = mSocket.getAncillaryFileDescriptors();
} catch (IOException ex) { } catch (IOException ex) {
Log.w(TAG, "IOException on command socket " + ex.getMessage()); throw new IllegalStateException("IOException on command socket", ex);
closeSocket();
return true;
} }
// readArgumentList returns null only when it has reached EOF with no available
// data to read. This will only happen when the remote socket has disconnected.
if (args == null) { if (args == null) {
// EOF reached. isEof = true;
closeSocket(); return null;
return true;
}
/** the stderr of the most recent request, if avail */
PrintStream newStderr = null;
if (descriptors != null && descriptors.length >= 3) {
newStderr = new PrintStream(
new FileOutputStream(descriptors[2]));
} }
int pid = -1; int pid = -1;
FileDescriptor childPipeFd = null; FileDescriptor childPipeFd = null;
FileDescriptor serverPipeFd = null; FileDescriptor serverPipeFd = null;
try {
parsedArgs = new Arguments(args); parsedArgs = new Arguments(args);
if (parsedArgs.abiListQuery) { if (parsedArgs.abiListQuery) {
return handleAbiListQuery(); handleAbiListQuery();
return null;
} }
if (parsedArgs.preloadDefault) { if (parsedArgs.preloadDefault) {
return handlePreload(); handlePreload();
return null;
} }
if (parsedArgs.preloadPackage != null) { if (parsedArgs.preloadPackage != null) {
return handlePreloadPackage(parsedArgs.preloadPackage, handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs,
parsedArgs.preloadPackageLibs, parsedArgs.preloadPackageCacheKey); parsedArgs.preloadPackageCacheKey);
return null;
} }
if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) { if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
@@ -194,11 +181,15 @@ class ZygoteConnection {
int[] fdsToIgnore = null; int[] fdsToIgnore = null;
if (parsedArgs.invokeWith != null) { if (parsedArgs.invokeWith != null) {
try {
FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC); FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
childPipeFd = pipeFds[1]; childPipeFd = pipeFds[1];
serverPipeFd = pipeFds[0]; serverPipeFd = pipeFds[0];
Os.fcntlInt(childPipeFd, F_SETFD, 0); Os.fcntlInt(childPipeFd, F_SETFD, 0);
fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()}; fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};
} catch (ErrnoException errnoEx) {
throw new IllegalStateException("Unable to set up pipe for invoke-with", errnoEx);
}
} }
/** /**
@@ -233,31 +224,24 @@ class ZygoteConnection {
parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet, parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
parsedArgs.appDataDir); parsedArgs.appDataDir);
} catch (ErrnoException ex) {
logAndPrintError(newStderr, "Exception creating pipe", ex);
} catch (IllegalArgumentException ex) {
logAndPrintError(newStderr, "Invalid zygote arguments", ex);
} catch (ZygoteSecurityException ex) {
logAndPrintError(newStderr,
"Zygote security policy prevents request: ", ex);
}
try { try {
if (pid == 0) { if (pid == 0) {
// in child // in child
zygoteServer.setForkChild();
zygoteServer.closeServerSocket(); zygoteServer.closeServerSocket();
IoUtils.closeQuietly(serverPipeFd); IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null; serverPipeFd = null;
handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
// should never get here, the child is expected to either return handleChildProc(parsedArgs, descriptors, childPipeFd);
// throw Zygote.MethodAndArgsCaller or exec().
return true;
} else { } else {
// in parent...pid of < 0 means failure // In the parent. A pid < 0 indicates a failure and will be handled in
// handleParentProc.
IoUtils.closeQuietly(childPipeFd); IoUtils.closeQuietly(childPipeFd);
childPipeFd = null; childPipeFd = null;
return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs); handleParentProc(pid, descriptors, serverPipeFd);
return null;
} }
} finally { } finally {
IoUtils.closeQuietly(childPipeFd); IoUtils.closeQuietly(childPipeFd);
@@ -265,15 +249,13 @@ class ZygoteConnection {
} }
} }
private boolean handleAbiListQuery() { private void handleAbiListQuery() {
try { try {
final byte[] abiListBytes = abiList.getBytes(StandardCharsets.US_ASCII); final byte[] abiListBytes = abiList.getBytes(StandardCharsets.US_ASCII);
mSocketOutStream.writeInt(abiListBytes.length); mSocketOutStream.writeInt(abiListBytes.length);
mSocketOutStream.write(abiListBytes); mSocketOutStream.write(abiListBytes);
return false;
} catch (IOException ioe) { } catch (IOException ioe) {
Log.e(TAG, "Error writing to command socket", ioe); throw new IllegalStateException("Error writing to command socket", ioe);
return true;
} }
} }
@@ -283,7 +265,7 @@ class ZygoteConnection {
* if no preload was initiated. The latter implies that the zygote is not configured to load * if no preload was initiated. The latter implies that the zygote is not configured to load
* resources lazy or that the zygote has already handled a previous request to handlePreload. * resources lazy or that the zygote has already handled a previous request to handlePreload.
*/ */
private boolean handlePreload() { private void handlePreload() {
try { try {
if (isPreloadComplete()) { if (isPreloadComplete()) {
mSocketOutStream.writeInt(1); mSocketOutStream.writeInt(1);
@@ -291,11 +273,8 @@ class ZygoteConnection {
preload(); preload();
mSocketOutStream.writeInt(0); mSocketOutStream.writeInt(0);
} }
return false;
} catch (IOException ioe) { } catch (IOException ioe) {
Log.e(TAG, "Error writing to command socket", ioe); throw new IllegalStateException("Error writing to command socket", ioe);
return true;
} }
} }
@@ -307,7 +286,11 @@ class ZygoteConnection {
return ZygoteInit.isPreloadComplete(); return ZygoteInit.isPreloadComplete();
} }
protected boolean handlePreloadPackage(String packagePath, String libsPath, String cacheKey) { protected DataOutputStream getSocketOutputStream() {
return mSocketOutStream;
}
protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
throw new RuntimeException("Zyogte does not support package preloading"); throw new RuntimeException("Zyogte does not support package preloading");
} }
@@ -323,6 +306,10 @@ class ZygoteConnection {
} }
} }
boolean isClosedByPeer() {
return isEof;
}
/** /**
* Handles argument parsing for args related to the zygote spawner. * Handles argument parsing for args related to the zygote spawner.
* *
@@ -753,15 +740,9 @@ class ZygoteConnection {
* @param parsedArgs non-null; zygote args * @param parsedArgs non-null; zygote args
* @param descriptors null-ok; new file descriptors for stdio if available. * @param descriptors null-ok; new file descriptors for stdio if available.
* @param pipeFd null-ok; pipe for communication back to Zygote. * @param pipeFd null-ok; pipe for communication back to Zygote.
* @param newStderr null-ok; stream to use for stderr until stdio
* is reopened.
*
* @throws Zygote.MethodAndArgsCaller on success to
* trampoline to code that invokes static main.
*/ */
private void handleChildProc(Arguments parsedArgs, private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr) FileDescriptor pipeFd) {
throws Zygote.MethodAndArgsCaller {
/** /**
* By the time we get here, the native code has closed the two actual Zygote * By the time we get here, the native code has closed the two actual Zygote
* socket connections, and substituted /dev/null in their place. The LocalSocket * socket connections, and substituted /dev/null in their place. The LocalSocket
@@ -778,7 +759,6 @@ class ZygoteConnection {
for (FileDescriptor fd: descriptors) { for (FileDescriptor fd: descriptors) {
IoUtils.closeQuietly(fd); IoUtils.closeQuietly(fd);
} }
newStderr = System.err;
} catch (ErrnoException ex) { } catch (ErrnoException ex) {
Log.e(TAG, "Error reopening stdio", ex); Log.e(TAG, "Error reopening stdio", ex);
} }
@@ -795,9 +775,12 @@ class ZygoteConnection {
parsedArgs.niceName, parsedArgs.targetSdkVersion, parsedArgs.niceName, parsedArgs.targetSdkVersion,
VMRuntime.getCurrentInstructionSet(), VMRuntime.getCurrentInstructionSet(),
pipeFd, parsedArgs.remainingArgs); pipeFd, parsedArgs.remainingArgs);
// Should not get here.
throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
} else { } else {
ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
parsedArgs.remainingArgs, null /* classLoader */); null /* classLoader */);
} }
} }
@@ -809,13 +792,8 @@ class ZygoteConnection {
* @param descriptors null-ok; file descriptors for child's new stdio if * @param descriptors null-ok; file descriptors for child's new stdio if
* specified. * specified.
* @param pipeFd null-ok; pipe for communication with child. * @param pipeFd null-ok; pipe for communication with child.
* @param parsedArgs non-null; zygote args
* @return true for "exit command loop" and false for "continue command
* loop"
*/ */
private boolean handleParentProc(int pid, private void handleParentProc(int pid, FileDescriptor[] descriptors, FileDescriptor pipeFd) {
FileDescriptor[] descriptors, FileDescriptor pipeFd, Arguments parsedArgs) {
if (pid > 0) { if (pid > 0) {
setChildPgid(pid); setChildPgid(pid);
} }
@@ -907,11 +885,8 @@ class ZygoteConnection {
mSocketOutStream.writeInt(pid); mSocketOutStream.writeInt(pid);
mSocketOutStream.writeBoolean(usingWrapper); mSocketOutStream.writeBoolean(usingWrapper);
} catch (IOException ex) { } catch (IOException ex) {
Log.e(TAG, "Error writing to command socket", ex); throw new IllegalStateException("Error writing to command socket", ex);
return true;
} }
return false;
} }
private void setChildPgid(int pid) { private void setChildPgid(int pid) {
@@ -927,20 +902,4 @@ class ZygoteConnection {
+ "normal if peer is not in our session"); + "normal if peer is not in our session");
} }
} }
/**
* Logs an error message and prints it to the specified stream, if
* provided
*
* @param newStderr null-ok; a standard error stream
* @param message non-null; error message
* @param ex null-ok an exception
*/
private static void logAndPrintError (PrintStream newStderr,
String message, Throwable ex) {
Log.e(TAG, message, ex);
if (newStderr != null) {
newStderr.println(message + (ex == null ? "" : ex));
}
}
} }

View File

@@ -24,7 +24,6 @@ import android.content.res.TypedArray;
import android.icu.impl.CacheValue; import android.icu.impl.CacheValue;
import android.icu.text.DecimalFormatSymbols; import android.icu.text.DecimalFormatSymbols;
import android.icu.util.ULocale; import android.icu.util.ULocale;
import android.net.LocalServerSocket;
import android.opengl.EGL14; import android.opengl.EGL14;
import android.os.Build; import android.os.Build;
import android.os.IInstalld; import android.os.IInstalld;
@@ -447,10 +446,7 @@ public class ZygoteInit {
/** /**
* Finish remaining work for the newly forked system server process. * Finish remaining work for the newly forked system server process.
*/ */
private static void handleSystemServerProcess( private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
ZygoteConnection.Arguments parsedArgs)
throws Zygote.MethodAndArgsCaller {
// set umask to 0077 so new files and directories will default to owner-only permissions. // set umask to 0077 so new files and directories will default to owner-only permissions.
Os.umask(S_IRWXG | S_IRWXO); Os.umask(S_IRWXG | S_IRWXO);
@@ -496,6 +492,8 @@ public class ZygoteInit {
WrapperInit.execApplication(parsedArgs.invokeWith, WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion, parsedArgs.niceName, parsedArgs.targetSdkVersion,
VMRuntime.getCurrentInstructionSet(), null, args); VMRuntime.getCurrentInstructionSet(), null, args);
throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
} else { } else {
ClassLoader cl = null; ClassLoader cl = null;
if (systemServerClasspath != null) { if (systemServerClasspath != null) {
@@ -507,7 +505,7 @@ public class ZygoteInit {
/* /*
* Pass the remaining arguments to SystemServer. * Pass the remaining arguments to SystemServer.
*/ */
ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl); return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
} }
/* should never reach here */ /* should never reach here */
@@ -589,10 +587,13 @@ public class ZygoteInit {
} }
/** /**
* Prepare the arguments and fork for the system server process. * Prepare the arguments and forks for the system server process.
*
* Returns an {@code Runnable} that provides an entrypoint into system_server code in the
* child process, and {@code null} in the parent.
*/ */
private static boolean startSystemServer(String abiList, String socketName, ZygoteServer zygoteServer) private static Runnable forkSystemServer(String abiList, String socketName,
throws Zygote.MethodAndArgsCaller, RuntimeException { ZygoteServer zygoteServer) {
long capabilities = posixCapabilitiesAsBits( long capabilities = posixCapabilitiesAsBits(
OsConstants.CAP_IPC_LOCK, OsConstants.CAP_IPC_LOCK,
OsConstants.CAP_KILL, OsConstants.CAP_KILL,
@@ -657,10 +658,10 @@ public class ZygoteInit {
} }
zygoteServer.closeServerSocket(); zygoteServer.closeServerSocket();
handleSystemServerProcess(parsedArgs); return handleSystemServerProcess(parsedArgs);
} }
return true; return null;
} }
/** /**
@@ -691,6 +692,7 @@ public class ZygoteInit {
throw new RuntimeException("Failed to setpgid(0,0)", ex); throw new RuntimeException("Failed to setpgid(0,0)", ex);
} }
final Runnable caller;
try { try {
// Report Zygote start time to tron unless it is a runtime restart // Report Zygote start time to tron unless it is a runtime restart
if (!"1".equals(SystemProperties.get("sys.boot_completed"))) { if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
@@ -760,19 +762,32 @@ public class ZygoteInit {
ZygoteHooks.stopZygoteNoThreadCreation(); ZygoteHooks.stopZygoteNoThreadCreation();
if (startSystemServer) { if (startSystemServer) {
startSystemServer(abiList, socketName, zygoteServer); Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
// {@code r == null} in the parent (zygote) process, and {@code r != null} in the
// child (system_server) process.
if (r != null) {
r.run();
return;
}
} }
Log.i(TAG, "Accepting command socket connections"); Log.i(TAG, "Accepting command socket connections");
zygoteServer.runSelectLoop(abiList);
zygoteServer.closeServerSocket(); // The select loop returns early in the child process after a fork and
} catch (Zygote.MethodAndArgsCaller caller) { // loops forever in the zygote.
caller.run(); caller = zygoteServer.runSelectLoop(abiList);
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e(TAG, "System zygote died with exception", ex); Log.e(TAG, "System zygote died with exception", ex);
zygoteServer.closeServerSocket();
throw ex; throw ex;
} finally {
zygoteServer.closeServerSocket();
}
// We're in the child process and have exited the select loop. Proceed to execute the
// command.
if (caller != null) {
caller.run();
} }
} }
@@ -830,8 +845,7 @@ public class ZygoteInit {
* @param targetSdkVersion target SDK version * @param targetSdkVersion target SDK version
* @param argv arg strings * @param argv arg strings
*/ */
public static final void zygoteInit(int targetSdkVersion, String[] argv, public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
ClassLoader classLoader) throws Zygote.MethodAndArgsCaller {
if (RuntimeInit.DEBUG) { if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote"); Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
} }
@@ -841,7 +855,7 @@ public class ZygoteInit {
RuntimeInit.commonInit(); RuntimeInit.commonInit();
ZygoteInit.nativeZygoteInit(); ZygoteInit.nativeZygoteInit();
RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader); return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
} }
private static final native void nativeZygoteInit(); private static final native void nativeZygoteInit();

View File

@@ -25,6 +25,7 @@ import android.system.ErrnoException;
import android.system.StructPollfd; import android.system.StructPollfd;
import android.util.Log; import android.util.Log;
import android.util.Slog;
import java.io.IOException; import java.io.IOException;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.util.ArrayList; import java.util.ArrayList;
@@ -45,9 +46,18 @@ class ZygoteServer {
private LocalServerSocket mServerSocket; private LocalServerSocket mServerSocket;
/**
* Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}.
*/
private boolean mIsForkChild;
ZygoteServer() { ZygoteServer() {
} }
void setForkChild() {
mIsForkChild = true;
}
/** /**
* Registers a server socket for zygote command connections * Registers a server socket for zygote command connections
* *
@@ -129,11 +139,8 @@ class ZygoteServer {
* Runs the zygote process's select loop. Accepts new connections as * Runs the zygote process's select loop. Accepts new connections as
* they happen, and reads commands from connections one spawn-request's * they happen, and reads commands from connections one spawn-request's
* worth at a time. * worth at a time.
*
* @throws Zygote.MethodAndArgsCaller in a child process when a main()
* should be executed.
*/ */
void runSelectLoop(String abiList) throws Zygote.MethodAndArgsCaller { Runnable runSelectLoop(String abiList) {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>(); ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>(); ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
@@ -156,17 +163,64 @@ class ZygoteServer {
if ((pollFds[i].revents & POLLIN) == 0) { if ((pollFds[i].revents & POLLIN) == 0) {
continue; continue;
} }
if (i == 0) { if (i == 0) {
ZygoteConnection newPeer = acceptCommandPeer(abiList); ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer); peers.add(newPeer);
fds.add(newPeer.getFileDesciptor()); fds.add(newPeer.getFileDesciptor());
} else { } else {
boolean done = peers.get(i).runOnce(this); try {
if (done) { ZygoteConnection connection = peers.get(i);
final Runnable command = connection.processOneCommand(this);
if (mIsForkChild) {
// We're in the child. We should always have a command to run at this
// stage if processOneCommand hasn't called "exec".
if (command == null) {
throw new IllegalStateException("command == null");
}
return command;
} else {
// We're in the server - we should never have any commands to run.
if (command != null) {
throw new IllegalStateException("command != null");
}
// We don't know whether the remote side of the socket was closed or
// not until we attempt to read from it from processOneCommand. This shows up as
// a regular POLLIN event in our regular processing loop.
if (connection.isClosedByPeer()) {
connection.closeSocket();
peers.remove(i); peers.remove(i);
fds.remove(i); fds.remove(i);
} }
} }
} catch (Exception e) {
if (!mIsForkChild) {
// We're in the server so any exception here is one that has taken place
// pre-fork while processing commands or reading / writing from the
// control socket. Make a loud noise about any such exceptions so that
// we know exactly what failed and why.
Slog.e(TAG, "Exception executing zygote command: ", e);
// Make sure the socket is closed so that the other end knows immediately
// that something has gone wrong and doesn't time out waiting for a
// response.
ZygoteConnection conn = peers.remove(i);
conn.closeSocket();
fds.remove(i);
} else {
// We're in the child so any exception caught here has happened post
// fork and before we execute ActivityThread.main (or any other main()
// method). Log the details of the exception and bring down the process.
Log.e(TAG, "Caught post-fork exception in child process.", e);
throw e;
}
}
}
} }
} }
} }