blob: 8486272637904259fa60a3e7174ec1a94b60f6fc [file] [log] [blame]
/*
* Copyright (C) 2006 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.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.os.Debug;
import android.os.IBinder;
import android.os.ICheckinService;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.server.data.CrashData;
import android.util.Config;
import android.util.Log;
import com.android.internal.logging.AndroidConfig;
import dalvik.system.VMRuntime;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.LogManager;
import java.util.TimeZone;
import org.apache.harmony.luni.internal.util.TimezoneGetter;
/**
* Main entry point for runtime initialization. Not for
* public consumption.
* @hide
*/
public class RuntimeInit {
private final static String TAG = "AndroidRuntime";
/** true if commonInit() has been called */
private static boolean initialized;
/**
* Use this to log a message when a thread exits due to an uncaught
* exception. The framework catches these for the main threads, so
* this should only matter for threads created by applications.
*/
private static class UncaughtHandler implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
try {
Log.e(TAG, "Uncaught handler: thread " + t.getName()
+ " exiting due to uncaught exception");
} catch (Throwable error) {
// Ignore the throwable, since we're in the process of crashing anyway.
// If we don't, the crash won't happen properly and the process will
// be left around in a bad state.
}
crash(TAG, e);
}
}
private static final void commonInit() {
if (Config.LOGV) Log.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) Log.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
if (hasQwerty == 1) {
System.setProperty("qwerty", "1");
}
/*
* Install a TimezoneGetter subclass for ZoneInfo.db
*/
TimezoneGetter.setInstance(new TimezoneGetter() {
@Override
public String getId() {
return SystemProperties.get("persist.sys.timezone");
}
});
TimeZone.setDefault(null);
/*
* Sets handler for java.util.logging to use Android log facilities.
* The odd "new instance-and-then-throw-away" is a mirror of how
* the "java.util.logging.config.class" system property works. We
* can't use the system property here since the logger has almost
* certainly already been initialized.
*/
LogManager.getLogManager().reset();
new AndroidConfig();
/*
* If we're running in an emulator launched with "-trace", put the
* VM into emulator trace profiling mode so that the user can hit
* F9/F10 at any time to capture traces. This has performance
* consequences, so it's not something you want to do always.
*/
String trace = SystemProperties.get("ro.kernel.android.tracing");
if (trace.equals("1")) {
Log.i(TAG, "NOTE: emulator trace profiling enabled");
Debug.enableEmulatorTraceOutput();
}
initialized = true;
}
/**
* Invokes a static "main(argv[]) method on class "className".
* Converts various failing exceptions into RuntimeExceptions, with
* the assumption that they will then cause the VM instance to exit.
*
* @param className Fully-qualified class name
* @param argv Argument vector for main()
*/
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 {
cl = Class.forName(className);
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
"Missing class when invoking static main " + className,
ex);
}
Method m;
try {
m = cl.getMethod("main", new Class[] { String[].class });
} catch (NoSuchMethodException ex) {
throw new RuntimeException(
"Missing static main on " + className, ex);
} catch (SecurityException ex) {
throw new RuntimeException(
"Problem getting static main on " + className, ex);
}
int modifiers = m.getModifiers();
if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
throw new RuntimeException(
"Main method is not public and static on " + className);
}
/*
* This throw gets caught in ZygoteInit.main(), which responds
* by invoking the exception's run() method. This arrangement
* clears up all the stack frames that were required in setting
* up the process.
*/
throw new ZygoteInit.MethodAndArgsCaller(m, argv);
}
public static final void main(String[] argv) {
commonInit();
/*
* Now that we're running in interpreted code, call back into native code
* to run the system.
*/
finishInit();
if (Config.LOGV) Log.d(TAG, "Leaving RuntimeInit!");
}
public static final native void finishInit();
/**
* The main function called when started through the zygote process. This
* could be unified with main(), if the native code in finishInit()
* were rationalized with Zygote startup.<p>
*
* 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>
*
* @param argv arg strings
*/
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"));
commonInit();
zygoteInitNative();
int curArg = 0;
for ( /* curArg */ ; curArg < argv.length; curArg++) {
String arg = argv[curArg];
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);
}
}
if (curArg == argv.length) {
Log.e(TAG, "Missing classname argument to RuntimeInit!");
// let the process exit
return;
}
// Remaining arguments are passed to the start class's static main
String startClass = argv[curArg++];
String[] startArgs = new String[argv.length - curArg];
System.arraycopy(argv, curArg, startArgs, 0, startArgs.length);
invokeStaticMain(startClass, startArgs);
}
public static final native void zygoteInitNative();
/**
* Returns 1 if the computer is on. If the computer isn't on, the value returned by this method is undefined.
*/
public static final native int isComputerOn();
/**
* Turns the computer on if the computer is off. If the computer is on, the behavior of this method is undefined.
*/
public static final native void turnComputerOn();
/**
*
* @return 1 if the device has a qwerty keyboard
*/
public static native int getQwertyKeyboard();
/**
* Report a fatal error in the current process. If this is a user-process,
* a dialog may be displayed informing the user of the error. This
* function does not return; it forces the current process to exit.
*
* @param tag to use when logging the error
* @param t exception that was generated by the error
*/
public static void crash(String tag, Throwable t) {
if (mApplicationObject != null) {
byte[] crashData = null;
try {
// Log exception.
Log.e(TAG, Log.getStackTraceString(t));
crashData = marshallException(tag, t);
if (crashData == null) {
throw new NullPointerException("Can't marshall crash data");
}
} catch (Throwable t2) {
try {
// Log exception as a string so we don't get in an infinite loop.
Log.e(TAG, "Error reporting crash: "
+ Log.getStackTraceString(t2));
} catch (Throwable t3) {
// Do nothing, must be OOM so we can't format the message
}
}
try {
// Display user-visible error message.
String msg = t.getMessage();
if (msg == null) {
msg = t.toString();
}
IActivityManager am = ActivityManagerNative.getDefault();
try {
int res = am.handleApplicationError(mApplicationObject,
0, tag, msg, t.toString(), crashData);
// Is waiting for the debugger the right thing?
// For now I have turned off the Debug button, because
// I'm not sure what we should do if it is actually
// selected.
//Log.i(TAG, "Got app error result: " + res);
if (res == 1) {
Debug.waitForDebugger();
return;
}
} catch (RemoteException e) {
}
} catch (Throwable t2) {
try {
// Log exception as a string so we don't get in an infinite loop.
Log.e(TAG, "Error reporting crash: "
+ Log.getStackTraceString(t2));
} catch (Throwable t3) {
// Do nothing, must be OOM so we can't format the message
}
} finally {
// Try everything to make sure this process goes away.
Process.killProcess(Process.myPid());
System.exit(10);
}
} else {
try {
Log.e(TAG, "*** EXCEPTION IN SYSTEM PROCESS. System will crash.");
Log.e(tag, Log.getStackTraceString(t));
reportException(tag, t, true); // synchronous
} catch (Throwable t2) {
// Do nothing, must be OOM so we can't format the message
} finally {
// Try everything to make sure this process goes away.
Process.killProcess(Process.myPid());
System.exit(10);
}
}
}
/** Counter used to prevent reentrancy in {@link #reportException}. */
private static final AtomicInteger sInReportException = new AtomicInteger();
/**
* Report an error in the current process. The exception information will
* be handed off to the checkin service and eventually uploaded for analysis.
* This is expensive! Only use this when the exception indicates a programming
* error ("should not happen").
*
* @param tag to use when logging the error
* @param t exception that was generated by the error
* @param sync true to wait for the report, false to "fire and forget"
*/
public static void reportException(String tag, Throwable t, boolean sync) {
if (!initialized) {
// Exceptions during, eg, zygote cannot use this mechanism
return;
}
// It's important to prevent an infinite crash-reporting loop:
// while this function is running, don't let it be called again.
int reenter = sInReportException.getAndIncrement();
if (reenter != 0) {
sInReportException.decrementAndGet();
Log.e(TAG, "Crash logging skipped, already logging another crash");
return;
}
// TODO: Enable callers to specify a level (i.e. warn or error).
try {
// Submit crash data to statistics service.
byte[] crashData = marshallException(tag, t);
ICheckinService checkin = ICheckinService.Stub.asInterface(
ServiceManager.getService("checkin"));
if (checkin == null) {
Log.e(TAG, "Crash logging skipped, no checkin service");
} else if (sync) {
checkin.reportCrashSync(crashData);
} else {
checkin.reportCrashAsync(crashData);
}
} catch (Throwable t2) {
// Log exception as a string so we don't get in an infinite loop.
Log.e(TAG, "Crash logging failed: " + t2);
} finally {
sInReportException.decrementAndGet();
}
}
private static byte[] marshallException(String tag, Throwable t) {
// Convert crash data to bytes.
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
new CrashData(tag, t).write(dout);
dout.close();
} catch (IOException e) {
return null;
}
return bout.toByteArray();
}
/**
* Replay an encoded CrashData record back into a useable CrashData record. This can be
* helpful for providing debugging output after a process error.
*
* @param crashDataBytes The byte array containing the encoded crash record
* @return new CrashData record, or null if could not create one.
*/
public static CrashData unmarshallException(byte[] crashDataBytes) {
try {
ByteArrayInputStream bin = new ByteArrayInputStream(crashDataBytes);
DataInputStream din = new DataInputStream(bin);
return new CrashData(din);
} catch (IOException e) {
return null;
}
}
/**
* Set the object identifying this application/process, for reporting VM
* errors.
*/
public static final void setApplicationObject(IBinder app) {
mApplicationObject = app;
}
/**
* Enable debugging features.
*/
static {
// Register handlers for DDM messages.
android.ddm.DdmRegister.registerHandlers();
}
private static IBinder mApplicationObject;
}