am 7e79b9aa: am dbff9107: Merge "Support wrapping app processes to inject debug instrumentation. Bug: 4437846"

* commit '7e79b9aad85db332d334f8bc1625bf46f030830f':
  Support wrapping app processes to inject debug instrumentation. Bug: 4437846
This commit is contained in:
Jeff Brown
2011-08-10 13:59:41 -07:00
committed by Android Git Automerger
13 changed files with 637 additions and 226 deletions

View File

@@ -128,10 +128,7 @@ int main(int argc, const char* const argv[])
mArgLen--;
AppRuntime runtime;
const char *arg;
const char *argv0;
argv0 = argv[0];
const char* argv0 = argv[0];
// Process command line arguments
// ignore argv[0]
@@ -142,39 +139,53 @@ int main(int argc, const char* const argv[])
int i = runtime.addVmArguments(argc, argv);
// Next arg is parent directory
if (i < argc) {
runtime.mParentDir = argv[i++];
// Parse runtime arguments. Stop at first unrecognized option.
bool zygote = false;
bool startSystemServer = false;
bool application = false;
const char* parentDir = NULL;
const char* niceName = NULL;
const char* className = NULL;
while (i < argc) {
const char* arg = argv[i++];
if (!parentDir) {
parentDir = arg;
} else if (strcmp(arg, "--zygote") == 0) {
zygote = true;
niceName = "zygote";
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName = arg + 12;
} else {
className = arg;
break;
}
}
// Next arg is startup classname or "--zygote"
if (i < argc) {
arg = argv[i++];
if (0 == strcmp("--zygote", arg)) {
bool startSystemServer = (i < argc) ?
strcmp(argv[i], "--start-system-server") == 0 : false;
setArgv0(argv0, "zygote");
set_process_name("zygote");
runtime.start("com.android.internal.os.ZygoteInit",
startSystemServer);
} else {
set_process_name(argv0);
if (niceName && *niceName) {
setArgv0(argv0, niceName);
set_process_name(niceName);
}
runtime.mClassName = arg;
runtime.mParentDir = parentDir;
// Remainder of args get passed to startup class main()
runtime.mArgC = argc-i;
runtime.mArgV = argv+i;
LOGV("App process is starting with pid=%d, class=%s.\n",
getpid(), runtime.getClassName());
runtime.start();
}
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit",
startSystemServer ? "start-system-server" : "");
} else if (className) {
// Remainder of args get passed to startup class main()
runtime.mClassName = className;
runtime.mArgC = argc - i;
runtime.mArgV = argv + i;
runtime.start("com.android.internal.os.RuntimeInit",
application ? "application" : "tool");
} else {
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
return 10;
}
}

View File

@@ -497,8 +497,8 @@ int main(int argc, char* const argv[])
} else {
#ifndef HAVE_ANDROID_OS
QuickRuntime* runt = new QuickRuntime();
runt->start("com/android/server/SystemServer",
false /* spontaneously fork system server from zygote */);
runt->start("com/android/server/SystemServer",
"" /* spontaneously fork system server from zygote */);
#endif
}

View File

@@ -257,13 +257,12 @@ public class Process {
* @param enableDebugger True if debugging should be enabled for this process.
* @param zygoteArgs Additional arguments to supply to the zygote process.
*
* @return int If > 0 the pid of the new process; if 0 the process is
* being emulated by a thread
* @return An object that describes the result of the attempt to start the process.
* @throws RuntimeException on fatal start failure
*
* {@hide}
*/
public static final int start(final String processClass,
public static final ProcessStartResult start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
int debugFlags,
@@ -294,8 +293,7 @@ public class Process {
} else {
new Thread(runnable).start();
}
return 0;
return new ProcessStartResult();
}
}
@@ -303,7 +301,7 @@ public class Process {
* Start a new process. Don't supply a custom nice name.
* {@hide}
*/
public static final int start(String processClass, int uid, int gid,
public static final ProcessStartResult start(String processClass, int uid, int gid,
int[] gids, int debugFlags, String[] zygoteArgs) {
return start(processClass, "", uid, gid, gids,
debugFlags, zygoteArgs);
@@ -418,14 +416,11 @@ public class Process {
* and returns the child's pid. Please note: the present implementation
* replaces newlines in the argument list with spaces.
* @param args argument list
* @return PID of new child process
* @return An object that describes the result of the attempt to start the process.
* @throws ZygoteStartFailedEx if process start failed for any reason
*/
private static int zygoteSendArgsAndGetPid(ArrayList<String> args)
private static ProcessStartResult zygoteSendArgsAndGetResult(ArrayList<String> args)
throws ZygoteStartFailedEx {
int pid;
openZygoteSocketIfNeeded();
try {
@@ -436,7 +431,8 @@ public class Process {
* b) a number of newline-separated argument strings equal to count
*
* After the zygote process reads these it will write the pid of
* the child or -1 on failure.
* the child or -1 on failure, followed by boolean to
* indicate whether a wrapper process was used.
*/
sZygoteWriter.write(Integer.toString(args.size()));
@@ -456,11 +452,13 @@ public class Process {
sZygoteWriter.flush();
// Should there be a timeout on this?
pid = sZygoteInputStream.readInt();
if (pid < 0) {
ProcessStartResult result = new ProcessStartResult();
result.pid = sZygoteInputStream.readInt();
if (result.pid < 0) {
throw new ZygoteStartFailedEx("fork() failed");
}
result.usingWrapper = sZygoteInputStream.readBoolean();
return result;
} catch (IOException ex) {
try {
if (sZygoteSocket != null) {
@@ -475,8 +473,6 @@ public class Process {
throw new ZygoteStartFailedEx(ex);
}
return pid;
}
/**
@@ -490,18 +486,16 @@ public class Process {
* new process should setgroup() to.
* @param enableDebugger True if debugging should be enabled for this process.
* @param extraArgs Additional arguments to supply to the zygote process.
* @return PID
* @return An object that describes the result of the attempt to start the process.
* @throws ZygoteStartFailedEx if process start failed for any reason
*/
private static int startViaZygote(final String processClass,
private static ProcessStartResult startViaZygote(final String processClass,
final String niceName,
final int uid, final int gid,
final int[] gids,
int debugFlags,
String[] extraArgs)
throws ZygoteStartFailedEx {
int pid;
synchronized(Process.class) {
ArrayList<String> argsForZygote = new ArrayList<String>();
@@ -553,15 +547,9 @@ public class Process {
argsForZygote.add(arg);
}
}
pid = zygoteSendArgsAndGetPid(argsForZygote);
}
if (pid <= 0) {
throw new ZygoteStartFailedEx("zygote start failed:" + pid);
return zygoteSendArgsAndGetResult(argsForZygote);
}
return pid;
}
/**
@@ -615,6 +603,20 @@ public class Process {
return (int) procStatusValues[0];
}
/**
* Returns the parent process id for a currently running process.
* @param pid the process id
* @return the parent process id of the process, or -1 if the process is not running.
* @hide
*/
public static final int getParentPid(int pid) {
String[] procStatusLabels = { "PPid:" };
long[] procStatusValues = new long[1];
procStatusValues[0] = -1;
Process.readProcLines("/proc/" + pid + "/status", procStatusLabels, procStatusValues);
return (int) procStatusValues[0];
}
/**
* Set the priority of a thread, based on Linux priorities.
*
@@ -826,4 +828,21 @@ public class Process {
* @hide
*/
public static final native long getPss(int pid);
/**
* Specifies the outcome of having started a process.
* @hide
*/
public static final class ProcessStartResult {
/**
* The PID of the newly started process.
* Always >= 0. (If the start failed, an exception will have been thrown instead.)
*/
public int pid;
/**
* True if the process was started with a wrapper attached.
*/
public boolean usingWrapper;
}
}

View File

@@ -23,7 +23,6 @@ import android.os.Debug;
import android.os.IBinder;
import android.os.Process;
import android.os.SystemProperties;
import android.util.Config;
import android.util.Log;
import android.util.Slog;
@@ -46,6 +45,7 @@ import org.apache.harmony.luni.internal.util.TimezoneGetter;
*/
public class RuntimeInit {
private final static String TAG = "AndroidRuntime";
private final static boolean DEBUG = false;
/** true if commonInit() has been called */
private static boolean initialized;
@@ -90,14 +90,14 @@ public class RuntimeInit {
}
private static final void commonInit() {
if (Config.LOGV) Slog.d(TAG, "Entered RuntimeInit!");
if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
/* set default handler; this applies to all threads in the VM */
Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());
int hasQwerty = getQwertyKeyboard();
if (Config.LOGV) Slog.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
if (DEBUG) Slog.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
if (hasQwerty == 1) {
System.setProperty("qwerty", "1");
}
@@ -184,11 +184,6 @@ public class RuntimeInit {
*/
private static void invokeStaticMain(String className, String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
// We want to be fairly aggressive about heap utilization, to avoid
// holding on to a lot of memory that isn't needed.
VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
Class<?> cl;
try {
@@ -226,6 +221,13 @@ public class RuntimeInit {
}
public static final void main(String[] argv) {
if (argv.length == 2 && argv[1].equals("application")) {
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application");
redirectLogStreams();
} else {
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting tool");
}
commonInit();
/*
@@ -234,7 +236,7 @@ public class RuntimeInit {
*/
finishInit();
if (Config.LOGV) Slog.d(TAG, "Leaving RuntimeInit!");
if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
}
public static final native void finishInit();
@@ -246,7 +248,6 @@ public class RuntimeInit {
*
* Current recognized args:
* <ul>
* <li> --nice-name=<i>nice name to appear in ps</i>
* <li> <code> [--] &lt;start class name&gt; &lt;args&gt;
* </ul>
*
@@ -254,43 +255,60 @@ public class RuntimeInit {
*/
public static final void zygoteInit(String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
// TODO: Doing this here works, but it seems kind of arbitrary. Find
// a better place. The goal is to set it up for applications, but not
// tools like am.
System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");
redirectLogStreams();
commonInit();
zygoteInitNative();
int curArg = 0;
for ( /* curArg */ ; curArg < argv.length; curArg++) {
String arg = argv[curArg];
applicationInit(argv);
}
if (arg.equals("--")) {
curArg++;
break;
} else if (!arg.startsWith("--")) {
break;
} else if (arg.startsWith("--nice-name=")) {
String niceName = arg.substring(arg.indexOf('=') + 1);
Process.setArgV0(niceName);
}
}
/**
* The main function called when an application is started through a
* wrapper process.
*
* When the wrapper starts, the runtime starts {@link RuntimeInit#main}
* which calls {@link WrapperInit#main} which then calls this method.
* So we don't need to call commonInit() here.
*
* @param argv arg strings
*/
public static void wrapperInit(String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from wrapper");
if (curArg == argv.length) {
Slog.e(TAG, "Missing classname argument to RuntimeInit!");
applicationInit(argv);
}
private static void applicationInit(String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
// We want to be fairly aggressive about heap utilization, to avoid
// holding on to a lot of memory that isn't needed.
VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
final Arguments args;
try {
args = new Arguments(argv);
} catch (IllegalArgumentException ex) {
Slog.e(TAG, ex.getMessage());
// let the process exit
return;
}
// Remaining arguments are passed to the start class's static main
invokeStaticMain(args.startClass, args.startArgs);
}
String startClass = argv[curArg++];
String[] startArgs = new String[argv.length - curArg];
System.arraycopy(argv, curArg, startArgs, 0, startArgs.length);
invokeStaticMain(startClass, startArgs);
/**
* Redirect System.out and System.err to the Android log.
*/
public static void redirectLogStreams() {
System.out.close();
System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
System.err.close();
System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
}
public static final native void zygoteInitNative();
@@ -353,4 +371,55 @@ public class RuntimeInit {
// Register handlers for DDM messages.
android.ddm.DdmRegister.registerHandlers();
}
/**
* Handles argument parsing for args related to the runtime.
*
* Current recognized args:
* <ul>
* <li> <code> [--] &lt;start class name&gt; &lt;args&gt;
* </ul>
*/
static class Arguments {
/** first non-option argument */
String startClass;
/** all following arguments */
String[] startArgs;
/**
* Constructs instance and parses args
* @param args runtime command-line args
* @throws IllegalArgumentException
*/
Arguments(String args[]) throws IllegalArgumentException {
parseArgs(args);
}
/**
* Parses the commandline arguments intended for the Runtime.
*/
private void parseArgs(String args[])
throws IllegalArgumentException {
int curArg = 0;
for (; curArg < args.length; curArg++) {
String arg = args[curArg];
if (arg.equals("--")) {
curArg++;
break;
} else if (!arg.startsWith("--")) {
break;
}
}
if (curArg == args.length) {
throw new IllegalArgumentException("Missing classname argument to RuntimeInit!");
}
startClass = args[curArg++];
startArgs = new String[args.length - curArg];
System.arraycopy(args, curArg, startArgs, 0, startArgs.length);
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.os;
import android.os.Process;
import android.util.Slog;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import dalvik.system.Zygote;
/**
* Startup class for the wrapper process.
* @hide
*/
public class WrapperInit {
private final static String TAG = "AndroidRuntime";
/**
* Class not instantiable.
*/
private WrapperInit() {
}
/**
* The main function called when starting a runtime application through a
* wrapper process instead of by forking Zygote.
*
* The first argument specifies the file descriptor for a pipe that should receive
* the pid of this process, or 0 if none. The remaining arguments are passed to
* the runtime.
*
* @param args The command-line arguments.
*/
public static void main(String[] args) {
try {
// Tell the Zygote what our actual PID is (since it only knows about the
// wrapper that it directly forked).
int fdNum = Integer.parseInt(args[0], 10);
if (fdNum != 0) {
try {
FileDescriptor fd = ZygoteInit.createFileDescriptor(fdNum);
DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
os.writeInt(Process.myPid());
os.close();
ZygoteInit.closeDescriptorQuietly(fd);
} catch (IOException ex) {
Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
}
}
// Mimic Zygote preloading.
ZygoteInit.preload();
// Launch the application.
String[] runtimeArgs = new String[args.length - 1];
System.arraycopy(args, 1, runtimeArgs, 0, runtimeArgs.length);
RuntimeInit.wrapperInit(runtimeArgs);
} catch (ZygoteInit.MethodAndArgsCaller caller) {
caller.run();
}
}
/**
* Executes a runtime application with a wrapper command.
* This method never returns.
*
* @param invokeWith The wrapper command.
* @param niceName The nice name for the application, or null if none.
* @param pipeFd The pipe to which the application's pid should be written, or null if none.
* @param args Arguments for {@link RuntimeInit.main}.
*/
public static void execApplication(String invokeWith, String niceName,
FileDescriptor pipeFd, String[] args) {
StringBuilder command = new StringBuilder(invokeWith);
command.append(" /system/bin/app_process /system/bin --application");
if (niceName != null) {
command.append(" '--nice-name=").append(niceName).append("'");
}
command.append(" com.android.internal.os.WrapperInit ");
command.append(pipeFd != null ? ZygoteInit.getFDFromFileDescriptor(pipeFd) : 0);
Zygote.appendQuotedShellArgs(command, args);
Zygote.execShell(command.toString());
}
/**
* Executes a standalone application with a wrapper command.
* This method never returns.
*
* @param invokeWith The wrapper command.
* @param classPath The class path.
* @param className The class name to invoke.
* @param args Arguments for the main() method of the specified class.
*/
public static void execStandalone(String invokeWith, String classPath, String className,
String[] args) {
StringBuilder command = new StringBuilder(invokeWith);
command.append(" /system/bin/dalvikvm -classpath '").append(classPath);
command.append("' ").append(className);
Zygote.appendQuotedShellArgs(command, args);
Zygote.execShell(command.toString());
}
}

View File

@@ -26,8 +26,10 @@ import dalvik.system.PathClassLoader;
import dalvik.system.Zygote;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -193,15 +195,20 @@ class ZygoteConnection {
new FileOutputStream(descriptors[2]));
}
int pid;
int pid = -1;
FileDescriptor childPipeFd = null;
FileDescriptor serverPipeFd = null;
try {
parsedArgs = new Arguments(args);
applyUidSecurityPolicy(parsedArgs, peer);
applyDebuggerSecurityPolicy(parsedArgs);
applyRlimitSecurityPolicy(parsedArgs, peer);
applyCapabilitiesSecurityPolicy(parsedArgs, peer);
applyInvokeWithSecurityPolicy(parsedArgs, peer);
applyDebuggerSystemProperty(parsedArgs);
applyInvokeWithSystemProperty(parsedArgs);
int[][] rlimits = null;
@@ -209,25 +216,44 @@ class ZygoteConnection {
rlimits = parsedArgs.rlimits.toArray(intArray2d);
}
if (parsedArgs.runtimeInit && parsedArgs.invokeWith != null) {
FileDescriptor[] pipeFds = new FileDescriptor[2];
ZygoteInit.pipe(pipeFds);
childPipeFd = pipeFds[1];
serverPipeFd = pipeFds[0];
ZygoteInit.setCloseOnExec(serverPipeFd, true);
}
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids, parsedArgs.debugFlags, rlimits);
} catch (IOException ex) {
logAndPrintError(newStderr, "Exception creating pipe", ex);
} catch (IllegalArgumentException ex) {
logAndPrintError (newStderr, "Invalid zygote arguments", ex);
pid = -1;
logAndPrintError(newStderr, "Invalid zygote arguments", ex);
} catch (ZygoteSecurityException ex) {
logAndPrintError(newStderr,
"Zygote security policy prevents request: ", ex);
pid = -1;
}
if (pid == 0) {
// in child
handleChildProc(parsedArgs, descriptors, newStderr);
// should never happen
return true;
} else { /* pid != 0 */
// in parent...pid of < 0 means failure
return handleParentProc(pid, descriptors, parsedArgs);
try {
if (pid == 0) {
// in child
ZygoteInit.closeDescriptorQuietly(serverPipeFd);
serverPipeFd = null;
handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
// should never get here, the child is expected to either
// throw ZygoteInit.MethodAndArgsCaller or exec().
return true;
} else {
// in parent...pid of < 0 means failure
ZygoteInit.closeDescriptorQuietly(childPipeFd);
childPipeFd = null;
return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
}
} finally {
ZygoteInit.closeDescriptorQuietly(childPipeFd);
ZygoteInit.closeDescriptorQuietly(serverPipeFd);
}
}
@@ -244,8 +270,8 @@ class ZygoteConnection {
}
/**
* Handles argument parsing for args related to the zygote spawner.<p>
* Handles argument parsing for args related to the zygote spawner.
*
* Current recognized args:
* <ul>
* <li> --setuid=<i>uid of child process, defaults to 0</i>
@@ -274,6 +300,7 @@ class ZygoteConnection {
* be handed off to com.android.internal.os.RuntimeInit, rather than
* processed directly
* Android runtime startup (eg, Binder initialization) is also eschewed.
* <li> --nice-name=<i>nice name to appear in ps</i>
* <li> If <code>--runtime-init</code> is present:
* [--] &lt;args for RuntimeInit &gt;
* <li> If <code>--runtime-init</code> is absent:
@@ -307,6 +334,9 @@ class ZygoteConnection {
/** from --runtime-init */
boolean runtimeInit;
/** from --nice-name */
String niceName;
/** from --capabilities */
boolean capabilitiesSpecified;
long permittedCapabilities;
@@ -315,6 +345,9 @@ class ZygoteConnection {
/** from all --rlimit=r,c,m */
ArrayList<int[]> rlimits;
/** from --invoke-with */
String invokeWith;
/**
* Any args after and including the first non-option arg
* (or after a '--')
@@ -438,6 +471,23 @@ class ZygoteConnection {
for (int i = params.length - 1; i >= 0 ; i--) {
gids[i] = Integer.parseInt(params[i]);
}
} else if (arg.equals("--invoke-with")) {
if (invokeWith != null) {
throw new IllegalArgumentException(
"Duplicate arg specified");
}
try {
invokeWith = args[++curArg];
} catch (IndexOutOfBoundsException ex) {
throw new IllegalArgumentException(
"--invoke-with requires argument");
}
} else if (arg.startsWith("--nice-name=")) {
if (niceName != null) {
throw new IllegalArgumentException(
"Duplicate arg specified");
}
niceName = arg.substring(arg.indexOf('=') + 1);
} else {
break;
}
@@ -567,14 +617,15 @@ class ZygoteConnection {
/**
* Applies debugger security policy.
* Applies debugger system properties to the zygote arguments.
*
* If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
* the debugger state is specified via the "--enable-debugger" flag
* in the spawn request.
*
* @param args non-null; zygote spawner args
*/
private static void applyDebuggerSecurityPolicy(Arguments args) {
public static void applyDebuggerSystemProperty(Arguments args) {
if ("1".equals(SystemProperties.get("ro.debuggable"))) {
args.debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
}
@@ -663,6 +714,49 @@ class ZygoteConnection {
}
}
/**
* Applies zygote security policy.
* Based on the credentials of the process issuing a zygote command:
* <ol>
* <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
* wrapper command.
* <li> Any other uid may not specify any invoke-with argument.
* </ul>
*
* @param args non-null; zygote spawner arguments
* @param peer non-null; peer credentials
* @throws ZygoteSecurityException
*/
private static void applyInvokeWithSecurityPolicy(Arguments args, Credentials peer)
throws ZygoteSecurityException {
int peerUid = peer.getUid();
if (args.invokeWith != null && peerUid != 0) {
throw new ZygoteSecurityException("Peer is not permitted to specify "
+ "an explicit invoke-with wrapper command");
}
}
/**
* Applies invoke-with system properties to the zygote arguments.
*
* @param parsedArgs non-null; zygote args
*/
public static void applyInvokeWithSystemProperty(Arguments args) {
if (args.invokeWith == null && args.niceName != null) {
if (args.niceName != null) {
String property = "wrap." + args.niceName;
if (property.length() > 31) {
property = property.substring(0, 31);
}
args.invokeWith = SystemProperties.get(property);
if (args.invokeWith != null && args.invokeWith.length() == 0) {
args.invokeWith = null;
}
}
}
}
/**
* Handles post-fork setup of child proc, closing sockets as appropriate,
* reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller
@@ -670,6 +764,7 @@ class ZygoteConnection {
*
* @param parsedArgs non-null; zygote args
* @param descriptors null-ok; new file descriptors for stdio if available.
* @param pipeFd null-ok; pipe for communication back to Zygote.
* @param newStderr null-ok; stream to use for stderr until stdio
* is reopened.
*
@@ -677,7 +772,7 @@ class ZygoteConnection {
* trampoline to code that invokes static main.
*/
private void handleChildProc(Arguments parsedArgs,
FileDescriptor[] descriptors, PrintStream newStderr)
FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
throws ZygoteInit.MethodAndArgsCaller {
/*
@@ -704,7 +799,7 @@ class ZygoteConnection {
descriptors[1], descriptors[2]);
for (FileDescriptor fd: descriptors) {
ZygoteInit.closeDescriptor(fd);
ZygoteInit.closeDescriptorQuietly(fd);
}
newStderr = System.err;
} catch (IOException ex) {
@@ -712,37 +807,48 @@ class ZygoteConnection {
}
}
if (parsedArgs.niceName != null) {
Process.setArgV0(parsedArgs.niceName);
}
if (parsedArgs.runtimeInit) {
RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
} else {
ClassLoader cloader;
if (parsedArgs.classpath != null) {
cloader
= new PathClassLoader(parsedArgs.classpath,
ClassLoader.getSystemClassLoader());
if (parsedArgs.invokeWith != null) {
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, pipeFd, parsedArgs.remainingArgs);
} else {
cloader = ClassLoader.getSystemClassLoader();
RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
}
} else {
String className;
try {
className = parsedArgs.remainingArgs[0];
} catch (ArrayIndexOutOfBoundsException ex) {
logAndPrintError (newStderr,
logAndPrintError(newStderr,
"Missing required class name argument", null);
return;
}
String[] mainArgs
= new String[parsedArgs.remainingArgs.length - 1];
String[] mainArgs = new String[parsedArgs.remainingArgs.length - 1];
System.arraycopy(parsedArgs.remainingArgs, 1,
mainArgs, 0, mainArgs.length);
try {
ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
} catch (RuntimeException ex) {
logAndPrintError (newStderr, "Error starting. ", ex);
if (parsedArgs.invokeWith != null) {
WrapperInit.execStandalone(parsedArgs.invokeWith,
parsedArgs.classpath, className, mainArgs);
} else {
ClassLoader cloader;
if (parsedArgs.classpath != null) {
cloader = new PathClassLoader(parsedArgs.classpath,
ClassLoader.getSystemClassLoader());
} else {
cloader = ClassLoader.getSystemClassLoader();
}
try {
ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
} catch (RuntimeException ex) {
logAndPrintError(newStderr, "Error starting.", ex);
}
}
}
}
@@ -754,40 +860,61 @@ class ZygoteConnection {
* if &lt; 0;
* @param descriptors null-ok; file descriptors for child's new stdio if
* specified.
* @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,
FileDescriptor[] descriptors, Arguments parsedArgs) {
FileDescriptor[] descriptors, FileDescriptor pipeFd, Arguments parsedArgs) {
if(pid > 0) {
// Try to move the new child into the peer's process group.
try {
ZygoteInit.setpgid(pid, ZygoteInit.getpgid(peer.getPid()));
} catch (IOException ex) {
// This exception is expected in the case where
// the peer is not in our session
// TODO get rid of this log message in the case where
// getsid(0) != getsid(peer.getPid())
Log.i(TAG, "Zygote: setpgid failed. This is "
+ "normal if peer is not in our session");
if (pid > 0) {
setChildPgid(pid);
}
if (descriptors != null) {
for (FileDescriptor fd: descriptors) {
ZygoteInit.closeDescriptorQuietly(fd);
}
}
try {
if (descriptors != null) {
for (FileDescriptor fd: descriptors) {
ZygoteInit.closeDescriptor(fd);
boolean usingWrapper = false;
if (pipeFd != null && pid > 0) {
DataInputStream is = new DataInputStream(new FileInputStream(pipeFd));
int innerPid = -1;
try {
innerPid = is.readInt();
} catch (IOException ex) {
Log.w(TAG, "Error reading pid from wrapped process, child may have died", ex);
} finally {
try {
is.close();
} catch (IOException ex) {
}
}
// Ensure that the pid reported by the wrapped process is either the
// child process that we forked, or a descendant of it.
if (innerPid > 0) {
int parentPid = innerPid;
while (parentPid > 0 && parentPid != pid) {
parentPid = Process.getParentPid(parentPid);
}
if (parentPid > 0) {
Log.i(TAG, "Wrapped process has pid " + innerPid);
pid = innerPid;
usingWrapper = true;
} else {
Log.w(TAG, "Wrapped process reported a pid that is not a child of "
+ "the process that we forked: childPid=" + pid
+ " innerPid=" + innerPid);
}
}
} catch (IOException ex) {
Log.e(TAG, "Error closing passed descriptors in "
+ "parent process", ex);
}
try {
mSocketOutStream.writeInt(pid);
mSocketOutStream.writeBoolean(usingWrapper);
} catch (IOException ex) {
Log.e(TAG, "Error reading from command socket", ex);
return true;
@@ -808,6 +935,20 @@ class ZygoteConnection {
return false;
}
private void setChildPgid(int pid) {
// Try to move the new child into the peer's process group.
try {
ZygoteInit.setpgid(pid, ZygoteInit.getpgid(peer.getPid()));
} catch (IOException ex) {
// This exception is expected in the case where
// the peer is not in our session
// TODO get rid of this log message in the case where
// getsid(0) != getsid(peer.getPid())
Log.i(TAG, "Zygote: setpgid failed. This is "
+ "normal if peer is not in our session");
}
}
/**
* Logs an error message and prints it to the specified stream, if
* provided

View File

@@ -22,6 +22,8 @@ import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.net.LocalServerSocket;
import android.os.Debug;
import android.os.FileUtils;
import android.os.Process;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Config;
@@ -67,7 +69,7 @@ public class ZygoteInit {
private static final int PRELOAD_GC_THRESHOLD = 50000;
public static final String USAGE_STRING =
" <\"true\"|\"false\" for startSystemServer>";
" <\"start-system-server\"|\"\" for startSystemServer>";
private static LocalServerSocket sServerSocket;
@@ -245,6 +247,11 @@ public class ZygoteInit {
}
}
static void preload() {
preloadClasses();
preloadResources();
}
/**
* Performs Zygote process initialization. Loads and initializes
* commonly used classes.
@@ -494,11 +501,20 @@ public class ZygoteInit {
closeServerSocket();
/*
* Pass the remaining arguments to SystemServer.
* "--nice-name=system_server com.android.server.SystemServer"
*/
RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
if (parsedArgs.niceName != null) {
Process.setArgV0(parsedArgs.niceName);
}
if (parsedArgs.invokeWith != null) {
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, null, parsedArgs.remainingArgs);
} else {
/*
* Pass the remaining arguments to SystemServer.
*/
RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
}
/* should never reach here */
}
@@ -523,20 +539,13 @@ public class ZygoteInit {
try {
parsedArgs = new ZygoteConnection.Arguments(args);
/*
* Enable debugging of the system process if *either* the command line flags
* indicate it should be debuggable or the ro.debuggable system property
* is set to "1"
*/
int debugFlags = parsedArgs.debugFlags;
if ("1".equals(SystemProperties.get("ro.debuggable")))
debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
/* Request to fork the system server process */
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids, debugFlags, null,
parsedArgs.gids, parsedArgs.debugFlags, null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
@@ -561,9 +570,7 @@ public class ZygoteInit {
registerZygoteSocket();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());
preloadClasses();
//cacheRegisterMaps();
preloadResources();
preload();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
@@ -578,9 +585,9 @@ public class ZygoteInit {
throw new RuntimeException(argv[0] + USAGE_STRING);
}
if (argv[1].equals("true")) {
if (argv[1].equals("start-system-server")) {
startSystemServer();
} else if (!argv[1].equals("false")) {
} else if (!argv[1].equals("")) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}
@@ -760,6 +767,13 @@ public class ZygoteInit {
static native void closeDescriptor(FileDescriptor fd)
throws IOException;
static void closeDescriptorQuietly(FileDescriptor fd) {
try {
closeDescriptor(fd);
} catch (IOException e) {
}
}
/**
* Toggles the close-on-exec flag for the specified file descriptor.
*
@@ -810,6 +824,21 @@ public class ZygoteInit {
static native FileDescriptor createFileDescriptor(int fd)
throws IOException;
/**
* Gets the integer value of a file descriptor.
*
* @param fd non-null; file descriptor
* @return integer OS file descriptor
*/
static native int getFDFromFileDescriptor(FileDescriptor fd);
/**
* Creates a pipe.
*
* @param outFds non-null; an array to hold two file descriptors, initially null
*/
static native void pipe(FileDescriptor[] outFds) throws IOException;
/**
* Class not instantiable.
*/

View File

@@ -879,8 +879,11 @@ bail:
* Start the Android runtime. This involves starting the virtual machine
* and calling the "static void main(String[] args)" method in the class
* named by "className".
*
* Passes the main function two arguments, the class name and the specified
* options string.
*/
void AndroidRuntime::start(const char* className, const bool startSystemServer)
void AndroidRuntime::start(const char* className, const char* options)
{
LOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n",
className != NULL ? className : "(unknown)");
@@ -895,7 +898,7 @@ void AndroidRuntime::start(const char* className, const bool startSystemServer)
* 'startSystemServer == true' means runtime is obslete and not run from
* init.rc anymore, so we print out the boot start event here.
*/
if (startSystemServer) {
if (strcmp(options, "start-system-server") == 0) {
/* track our progress through the boot sequence */
const int LOG_BOOT_PROGRESS_START = 3000;
LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,
@@ -929,13 +932,13 @@ void AndroidRuntime::start(const char* className, const bool startSystemServer)
/*
* We want to call main() with a String array with arguments in it.
* At present we only have one argument, the class name. Create an
* array to hold it.
* At present we have two arguments, the class name and an option string.
* Create an array to hold them.
*/
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
jstring startSystemServerStr;
jstring optionsStr;
stringClass = env->FindClass("java/lang/String");
assert(stringClass != NULL);
@@ -944,9 +947,8 @@ void AndroidRuntime::start(const char* className, const bool startSystemServer)
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
startSystemServerStr = env->NewStringUTF(startSystemServer ?
"true" : "false");
env->SetObjectArrayElement(strArray, 1, startSystemServerStr);
optionsStr = env->NewStringUTF(options);
env->SetObjectArrayElement(strArray, 1, optionsStr);
/*
* Start VM. This thread becomes the main thread of the VM, and will
@@ -990,12 +992,6 @@ bail:
free(slashClassName);
}
void AndroidRuntime::start()
{
start("com.android.internal.os.RuntimeInit",
false /* Don't start the system server */);
}
void AndroidRuntime::onExit(int code)
{
LOGV("AndroidRuntime onExit calling exit(%d)", code);

View File

@@ -145,6 +145,10 @@ static void com_android_internal_os_ZygoteInit_reopenStdio(JNIEnv* env,
static void com_android_internal_os_ZygoteInit_closeDescriptor(JNIEnv* env,
jobject clazz, jobject descriptor)
{
if (!descriptor) {
return;
}
int fd;
int err;
@@ -327,6 +331,30 @@ static jobject com_android_internal_os_ZygoteInit_createFileDescriptor (
return jniCreateFileDescriptor(env, fd);
}
static jint com_android_internal_os_ZygoteInit_getFDFromFileDescriptor (
JNIEnv *env, jobject clazz, jobject fd)
{
return jniGetFDFromFileDescriptor(env, fd);
}
static void com_android_internal_os_ZygoteInit_pipe (
JNIEnv *env, jobject clazz, jobjectArray outFds)
{
int fds[2];
if (pipe(fds) < 0) {
jniThrowIOException(env, errno);
return;
}
for (int i = 0; i < 2; i++) {
jobject fdObj = jniCreateFileDescriptor(env, fds[i]);
if (env->ExceptionCheck()) {
return;
}
env->SetObjectArrayElement(outFds, i, fdObj);
}
}
/*
* JNI registration.
*/
@@ -355,7 +383,11 @@ static JNINativeMethod gMethods[] = {
{ "selectReadable", "([Ljava/io/FileDescriptor;)I",
(void *) com_android_internal_os_ZygoteInit_selectReadable },
{ "createFileDescriptor", "(I)Ljava/io/FileDescriptor;",
(void *) com_android_internal_os_ZygoteInit_createFileDescriptor }
(void *) com_android_internal_os_ZygoteInit_createFileDescriptor },
{ "getFDFromFileDescriptor", "(Ljava/io/FileDescriptor;)I",
(void *) com_android_internal_os_ZygoteInit_getFDFromFileDescriptor },
{ "pipe", "([Ljava/io/FileDescriptor;)V",
(void *) com_android_internal_os_ZygoteInit_pipe },
};
int register_com_android_internal_os_ZygoteInit(JNIEnv* env)
{

View File

@@ -37,6 +37,13 @@ public:
AndroidRuntime();
virtual ~AndroidRuntime();
enum StartMode {
Zygote,
SystemServer,
Application,
Tool,
};
/**
* Register a set of methods in the specified class.
*/
@@ -61,8 +68,7 @@ public:
int addVmArguments(int argc, const char* const argv[]);
void start(const char *classname, const bool startSystemServer);
void start(); // start in android.util.RuntimeInit
void start(const char *classname, const char* options);
static AndroidRuntime* getRuntime();

View File

@@ -203,6 +203,12 @@ public final class ActivityManagerService extends ActivityManagerNative
// before we decide it's never going to come up for real.
static final int PROC_START_TIMEOUT = 10*1000;
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real, when the process was
// started with a wrapper for instrumentation (such as Valgrind) because it
// could take much longer than usual.
static final int PROC_START_TIMEOUT_WITH_WRAPPER = 300*1000;
// How long to wait after going idle before forcing apps to GC.
static final int GC_TIMEOUT = 5*1000;
@@ -841,14 +847,6 @@ public final class ActivityManagerService extends ActivityManagerNative
* Current sequence id for process LRU updating.
*/
int mLruSeq = 0;
/**
* Set to true if the ANDROID_SIMPLE_PROCESS_MANAGEMENT envvar
* is set, indicating the user wants processes started in such a way
* that they can use ANDROID_PROCESS_WRAPPER and know what will be
* running in each process (thus no pre-initialized process, etc).
*/
boolean mSimpleProcessManagement = false;
/**
* System monitoring: number of processes that died since the last
@@ -1395,15 +1393,6 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private ActivityManagerService() {
String v = System.getenv("ANDROID_SIMPLE_PROCESS_MANAGEMENT");
if (v != null && Integer.getInteger(v) != 0) {
mSimpleProcessManagement = true;
}
v = System.getenv("ANDROID_DEBUG_APP");
if (v != null) {
mSimpleProcessManagement = true;
}
Slog.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass());
File dataDir = Environment.getDataDirectory();
@@ -1872,9 +1861,11 @@ public final class ActivityManagerService extends ActivityManagerNative
if ("1".equals(SystemProperties.get("debug.assert"))) {
debugFlags |= Zygote.DEBUG_ENABLE_ASSERT;
}
int pid = Process.start("android.app.ActivityThread",
mSimpleProcessManagement ? app.processName : null, uid, uid,
gids, debugFlags, null);
// Start the process. It will either succeed and return a result containing
// the PID of the new process, or else throw a RuntimeException.
Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread",
app.processName, uid, uid, gids, debugFlags, null);
BatteryStatsImpl bs = app.batteryStats.getBatteryStats();
synchronized (bs) {
if (bs.isOnBattery()) {
@@ -1882,12 +1873,12 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
EventLog.writeEvent(EventLogTags.AM_PROC_START, pid, uid,
EventLog.writeEvent(EventLogTags.AM_PROC_START, startResult.pid, uid,
app.processName, hostingType,
hostingNameStr != null ? hostingNameStr : "");
if (app.persistent) {
Watchdog.getInstance().processStarted(app.processName, pid);
Watchdog.getInstance().processStarted(app.processName, startResult.pid);
}
StringBuilder buf = mStringBuilder;
@@ -1901,7 +1892,7 @@ public final class ActivityManagerService extends ActivityManagerNative
buf.append(hostingNameStr);
}
buf.append(": pid=");
buf.append(pid);
buf.append(startResult.pid);
buf.append(" uid=");
buf.append(uid);
buf.append(" gids={");
@@ -1914,26 +1905,22 @@ public final class ActivityManagerService extends ActivityManagerNative
}
buf.append("}");
Slog.i(TAG, buf.toString());
if (pid == 0 || pid == MY_PID) {
if (startResult.pid == 0 || startResult.pid == MY_PID) {
// Processes are being emulated with threads.
app.pid = MY_PID;
app.removed = false;
mStartingProcesses.add(app);
} else if (pid > 0) {
app.pid = pid;
} else {
app.pid = startResult.pid;
app.usingWrapper = startResult.usingWrapper;
app.removed = false;
synchronized (mPidsSelfLocked) {
this.mPidsSelfLocked.put(pid, app);
this.mPidsSelfLocked.put(startResult.pid, app);
Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
msg.obj = app;
mHandler.sendMessageDelayed(msg, PROC_START_TIMEOUT);
mHandler.sendMessageDelayed(msg, startResult.usingWrapper
? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
}
} else {
app.pid = 0;
RuntimeException e = new RuntimeException(
"Failure starting process " + app.processName
+ ": returned pid=" + pid);
Slog.e(TAG, e.getMessage(), e);
}
} catch (RuntimeException e) {
// XXX do better error recovery.

View File

@@ -554,12 +554,12 @@ class ActivityRecord extends IApplicationToken.Stub {
public long getKeyDispatchingTimeout() {
synchronized(service) {
ActivityRecord r = getWaitingHistoryRecordLocked();
if (r == null || r.app == null
|| r.app.instrumentationClass == null) {
return ActivityManagerService.KEY_DISPATCHING_TIMEOUT;
if (r != null && r.app != null
&& (r.app.instrumentationClass != null || r.app.usingWrapper)) {
return ActivityManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT;
}
return ActivityManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT;
return ActivityManagerService.KEY_DISPATCHING_TIMEOUT;
}
}

View File

@@ -74,6 +74,7 @@ class ProcessRecord {
IInstrumentationWatcher instrumentationWatcher; // who is waiting
Bundle instrumentationArguments;// as given to us
ComponentName instrumentationResultClass;// copy of instrumentationClass
boolean usingWrapper; // Set to true when process was launched with a wrapper attached
BroadcastRecord curReceiver;// receiver currently running in the app
long lastWakeTime; // How long proc held wake lock at last check
long lastCpuTime; // How long proc has run CPU at last check