Merge commit 'b75a263239ff48c87b826f5d62a53c0efd22b507' into HEAD
Change-Id: Ia37a6c18e09e6bbfab4beadc5ed86f92b766fe21
diff --git a/Android.mk b/Android.mk
index adde173..140509c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,7 +17,6 @@
subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
libdex \
vm \
- dalvikvm \
dexgen \
dexlist \
dexopt \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 3fbdc64..2894484 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -54,3 +54,4 @@
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
+$(call add-clean-step, rm -rf $(OUT)/obj/STATIC_LIBRARIES/libdex_intermediates/import_includes)
diff --git a/dalvikvm/Android.mk b/dalvikvm/Android.mk
deleted file mode 100644
index e526098..0000000
--- a/dalvikvm/Android.mk
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright (C) 2008 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.
-
-
-LOCAL_PATH:= $(call my-dir)
-
-#
-# Common definitions.
-#
-
-dalvikvm_src_files := \
- Main.cpp
-
-dalvikvm_c_includes := \
- dalvik/include
-
-
-#
-# Build for the target (device).
-#
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(dalvikvm_src_files)
-LOCAL_C_INCLUDES := $(dalvikvm_c_includes)
-
-LOCAL_SHARED_LIBRARIES := \
- libdvm \
- libssl \
- libz
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := dalvikvm
-
-include $(BUILD_EXECUTABLE)
-
-
-#
-# Build for the host.
-#
-
-ifeq ($(WITH_HOST_DALVIK),true)
-
- include $(CLEAR_VARS)
- LOCAL_SRC_FILES := $(dalvikvm_src_files)
- LOCAL_C_INCLUDES := $(dalvikvm_c_includes)
-
- ifeq ($(HOST_OS)-$(HOST_ARCH),darwin-x86)
- # OS X comes with all these libraries, so there is no need
- # to build any of them. Note: OpenSSL consists of libssl
- # and libcrypto.
- LOCAL_LDLIBS := -lffi -lssl -lcrypto -lz
- else
- LOCAL_LDLIBS += -ldl -lpthread
- LOCAL_SHARED_LIBRARIES += libdvm libcrypto-host libicuuc-host libicui18n-host libssl-host
- endif
-
- LOCAL_MODULE_TAGS := optional
- LOCAL_MODULE := dalvikvm
-
- include $(BUILD_HOST_EXECUTABLE)
-
-endif
diff --git a/dalvikvm/Main.cpp b/dalvikvm/Main.cpp
deleted file mode 100644
index 4aa6e20..0000000
--- a/dalvikvm/Main.cpp
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-/*
- * Command-line invocation of the Dalvik VM.
- */
-#include "jni.h"
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <signal.h>
-#include <assert.h>
-
-
-/*
- * We want failed write() calls to just return with an error.
- */
-static void blockSigpipe()
-{
- sigset_t mask;
-
- sigemptyset(&mask);
- sigaddset(&mask, SIGPIPE);
- if (sigprocmask(SIG_BLOCK, &mask, NULL) != 0)
- fprintf(stderr, "WARNING: SIGPIPE not blocked\n");
-}
-
-/*
- * Create a String[] and populate it with the contents of argv.
- */
-static jobjectArray createStringArray(JNIEnv* env, char* const argv[], int argc)
-{
- jclass stringClass = NULL;
- jobjectArray strArray = NULL;
- jobjectArray result = NULL;
- int i;
-
- stringClass = env->FindClass("java/lang/String");
- if (env->ExceptionCheck()) {
- fprintf(stderr, "Got exception while finding class String\n");
- goto bail;
- }
- assert(stringClass != NULL);
- strArray = env->NewObjectArray(argc, stringClass, NULL);
- if (env->ExceptionCheck()) {
- fprintf(stderr, "Got exception while creating String array\n");
- goto bail;
- }
- assert(strArray != NULL);
-
- for (i = 0; i < argc; i++) {
- jstring argStr;
-
- argStr = env->NewStringUTF(argv[i]);
- if (env->ExceptionCheck()) {
- fprintf(stderr, "Got exception while allocating Strings\n");
- goto bail;
- }
- assert(argStr != NULL);
- env->SetObjectArrayElement(strArray, i, argStr);
- env->DeleteLocalRef(argStr);
- }
-
- /* return the array, and ensure we don't delete the local ref to it */
- result = strArray;
- strArray = NULL;
-
-bail:
- env->DeleteLocalRef(stringClass);
- env->DeleteLocalRef(strArray);
- return result;
-}
-
-/*
- * Determine whether or not the specified method is public.
- *
- * Returns JNI_TRUE on success, JNI_FALSE on failure.
- */
-static int methodIsPublic(JNIEnv* env, jclass clazz, jmethodID methodId)
-{
- static const int PUBLIC = 0x0001; // java.lang.reflect.Modifiers.PUBLIC
- jobject refMethod = NULL;
- jclass methodClass = NULL;
- jmethodID getModifiersId;
- int modifiers;
- int result = JNI_FALSE;
-
- refMethod = env->ToReflectedMethod(clazz, methodId, JNI_FALSE);
- if (refMethod == NULL) {
- fprintf(stderr, "Dalvik VM unable to get reflected method\n");
- goto bail;
- }
-
- /*
- * We now have a Method instance. We need to call
- * its getModifiers() method.
- */
- methodClass = env->FindClass("java/lang/reflect/Method");
- if (methodClass == NULL) {
- fprintf(stderr, "Dalvik VM unable to find class Method\n");
- goto bail;
- }
- getModifiersId = env->GetMethodID(methodClass,
- "getModifiers", "()I");
- if (getModifiersId == NULL) {
- fprintf(stderr, "Dalvik VM unable to find reflect.Method.getModifiers\n");
- goto bail;
- }
-
- modifiers = env->CallIntMethod(refMethod, getModifiersId);
- if ((modifiers & PUBLIC) == 0) {
- fprintf(stderr, "Dalvik VM: main() is not public\n");
- goto bail;
- }
-
- result = JNI_TRUE;
-
-bail:
- env->DeleteLocalRef(refMethod);
- env->DeleteLocalRef(methodClass);
- return result;
-}
-
-/*
- * Parse arguments. Most of it just gets passed through to the VM. The
- * JNI spec defines a handful of standard arguments.
- */
-int main(int argc, char* const argv[])
-{
- JavaVM* vm = NULL;
- JNIEnv* env = NULL;
- JavaVMInitArgs initArgs;
- JavaVMOption* options = NULL;
- char* slashClass = NULL;
- int optionCount, curOpt, i, argIdx;
- int needExtra = JNI_FALSE;
- int result = 1;
-
- setvbuf(stdout, NULL, _IONBF, 0);
-
- /* ignore argv[0] */
- argv++;
- argc--;
-
- /*
- * If we're adding any additional stuff, e.g. function hook specifiers,
- * add them to the count here.
- *
- * We're over-allocating, because this includes the options to the VM
- * plus the options to the program.
- */
- optionCount = argc;
-
- options = (JavaVMOption*) malloc(sizeof(JavaVMOption) * optionCount);
- memset(options, 0, sizeof(JavaVMOption) * optionCount);
-
- /*
- * Copy options over. Everything up to the name of the class starts
- * with a '-' (the function hook stuff is strictly internal).
- *
- * [Do we need to catch & handle "-jar" here?]
- */
- for (curOpt = argIdx = 0; argIdx < argc; argIdx++) {
- if (argv[argIdx][0] != '-' && !needExtra)
- break;
- options[curOpt++].optionString = strdup(argv[argIdx]);
-
- /* some options require an additional arg */
- needExtra = JNI_FALSE;
- if (strcmp(argv[argIdx], "-classpath") == 0 ||
- strcmp(argv[argIdx], "-cp") == 0)
- /* others? */
- {
- needExtra = JNI_TRUE;
- }
- }
-
- if (needExtra) {
- fprintf(stderr, "Dalvik VM requires value after last option flag\n");
- goto bail;
- }
-
- /* insert additional internal options here */
-
- assert(curOpt <= optionCount);
-
- initArgs.version = JNI_VERSION_1_4;
- initArgs.options = options;
- initArgs.nOptions = curOpt;
- initArgs.ignoreUnrecognized = JNI_FALSE;
-
- //printf("nOptions = %d\n", initArgs.nOptions);
-
- blockSigpipe();
-
- /*
- * Start VM. The current thread becomes the main thread of the VM.
- */
- if (JNI_CreateJavaVM(&vm, &env, &initArgs) < 0) {
- fprintf(stderr, "Dalvik VM init failed (check log file)\n");
- goto bail;
- }
-
- /*
- * Make sure they provided a class name. We do this after VM init
- * so that things like "-Xrunjdwp:help" have the opportunity to emit
- * a usage statement.
- */
- if (argIdx == argc) {
- fprintf(stderr, "Dalvik VM requires a class name\n");
- goto bail;
- }
-
- /*
- * We want to call main() with a String array with our arguments in it.
- * Create an array and populate it. Note argv[0] is not included.
- */
- jobjectArray strArray;
- strArray = createStringArray(env, &argv[argIdx+1], argc-argIdx-1);
- if (strArray == NULL)
- goto bail;
-
- /*
- * Find [class].main(String[]).
- */
- jclass startClass;
- jmethodID startMeth;
- char* cp;
-
- /* convert "com.android.Blah" to "com/android/Blah" */
- slashClass = strdup(argv[argIdx]);
- for (cp = slashClass; *cp != '\0'; cp++)
- if (*cp == '.')
- *cp = '/';
-
- startClass = env->FindClass(slashClass);
- if (startClass == NULL) {
- fprintf(stderr, "Dalvik VM unable to locate class '%s'\n", slashClass);
- goto bail;
- }
-
- startMeth = env->GetStaticMethodID(startClass,
- "main", "([Ljava/lang/String;)V");
- if (startMeth == NULL) {
- fprintf(stderr, "Dalvik VM unable to find static main(String[]) in '%s'\n",
- slashClass);
- goto bail;
- }
-
- /*
- * Make sure the method is public. JNI doesn't prevent us from calling
- * a private method, so we have to check it explicitly.
- */
- if (!methodIsPublic(env, startClass, startMeth))
- goto bail;
-
- /*
- * Invoke main().
- */
- env->CallStaticVoidMethod(startClass, startMeth, strArray);
-
- if (!env->ExceptionCheck())
- result = 0;
-
-bail:
- /*printf("Shutting down Dalvik VM\n");*/
- if (vm != NULL) {
- /*
- * This allows join() and isAlive() on the main thread to work
- * correctly, and also provides uncaught exception handling.
- */
- if (vm->DetachCurrentThread() != JNI_OK) {
- fprintf(stderr, "Warning: unable to detach main thread\n");
- result = 1;
- }
-
- if (vm->DestroyJavaVM() != 0)
- fprintf(stderr, "Warning: Dalvik VM did not shut down cleanly\n");
- /*printf("\nDalvik VM has exited\n");*/
- }
-
- for (i = 0; i < optionCount; i++)
- free((char*) options[i].optionString);
- free(options);
- free(slashClass);
- /*printf("--- VM is down, process exiting\n");*/
- return result;
-}
diff --git a/dx/src/com/android/dx/Version.java b/dx/src/com/android/dx/Version.java
index 130025a..e252996 100644
--- a/dx/src/com/android/dx/Version.java
+++ b/dx/src/com/android/dx/Version.java
@@ -21,5 +21,5 @@
*/
public class Version {
/** {@code non-null;} version string */
- public static final String VERSION = "1.7";
+ public static final String VERSION = "1.8";
}
diff --git a/dx/src/com/android/dx/cf/direct/ClassPathOpener.java b/dx/src/com/android/dx/cf/direct/ClassPathOpener.java
index e2e2cfb..c9fe275 100644
--- a/dx/src/com/android/dx/cf/direct/ClassPathOpener.java
+++ b/dx/src/com/android/dx/cf/direct/ClassPathOpener.java
@@ -17,6 +17,7 @@
package com.android.dx.cf.direct;
import com.android.dex.util.FileUtils;
+
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
@@ -45,6 +46,7 @@
* package.
*/
private final boolean sort;
+ private FileNameFilter filter;
/**
* Callback interface for {@code ClassOpener}.
@@ -82,6 +84,25 @@
}
/**
+ * Filter interface for {@code ClassOpener}.
+ */
+ public interface FileNameFilter {
+
+ boolean accept(String path);
+ }
+
+ /**
+ * An accept all filter.
+ */
+ public static final FileNameFilter acceptAll = new FileNameFilter() {
+
+ @Override
+ public boolean accept(String path) {
+ return true;
+ }
+ };
+
+ /**
* Constructs an instance.
*
* @param pathname {@code non-null;} path element to process
@@ -91,9 +112,24 @@
* @param consumer {@code non-null;} callback interface
*/
public ClassPathOpener(String pathname, boolean sort, Consumer consumer) {
+ this(pathname, sort, acceptAll, consumer);
+ }
+
+ /**
+ * Constructs an instance.
+ *
+ * @param pathname {@code non-null;} path element to process
+ * @param sort if true, sort such that classes appear before their inner
+ * classes and "package-info" occurs before all other classes in that
+ * package.
+ * @param consumer {@code non-null;} callback interface
+ */
+ public ClassPathOpener(String pathname, boolean sort, FileNameFilter filter,
+ Consumer consumer) {
this.pathname = pathname;
this.sort = sort;
this.consumer = consumer;
+ this.filter = filter;
}
/**
@@ -129,9 +165,12 @@
path.endsWith(".apk")) {
return processArchive(file);
}
-
- byte[] bytes = FileUtils.readFile(file);
- return consumer.processFileBytes(path, file.lastModified(), bytes);
+ if (filter.accept(path)) {
+ byte[] bytes = FileUtils.readFile(file);
+ return consumer.processFileBytes(path, file.lastModified(), bytes);
+ } else {
+ return false;
+ }
} catch (Exception ex) {
consumer.onException(ex);
return false;
@@ -226,22 +265,24 @@
}
String path = one.getName();
- InputStream in = zip.getInputStream(one);
+ if (filter.accept(path)) {
+ InputStream in = zip.getInputStream(one);
- baos.reset();
- for (;;) {
- int amt = in.read(buf);
- if (amt < 0) {
- break;
+ baos.reset();
+ for (;;) {
+ int amt = in.read(buf);
+ if (amt < 0) {
+ break;
+ }
+
+ baos.write(buf, 0, amt);
}
- baos.write(buf, 0, amt);
+ in.close();
+
+ byte[] bytes = baos.toByteArray();
+ any |= consumer.processFileBytes(path, one.getTime(), bytes);
}
-
- in.close();
-
- byte[] bytes = baos.toByteArray();
- any |= consumer.processFileBytes(path, one.getTime(), bytes);
}
zip.close();
diff --git a/dx/src/com/android/dx/cf/direct/DirectClassFile.java b/dx/src/com/android/dx/cf/direct/DirectClassFile.java
index 2af2efe..f908547 100644
--- a/dx/src/com/android/dx/cf/direct/DirectClassFile.java
+++ b/dx/src/com/android/dx/cf/direct/DirectClassFile.java
@@ -229,6 +229,15 @@
}
/**
+ * Gets the path where this class file is located.
+ *
+ * @return {@code non-null;} the filePath
+ */
+ public String getFilePath() {
+ return filePath;
+ }
+
+ /**
* Gets the {@link ByteArray} that this instance's data comes from.
*
* @return {@code non-null;} the bytes
diff --git a/dx/src/com/android/dx/command/Main.java b/dx/src/com/android/dx/command/Main.java
index 6540e35..1132edb 100644
--- a/dx/src/com/android/dx/command/Main.java
+++ b/dx/src/com/android/dx/command/Main.java
@@ -34,12 +34,24 @@
" [--dump-method=<name>[*]] [--verbose-dump] [--no-files] " +
"[--core-library]\n" +
" [--num-threads=<n>] [--incremental] [--force-jumbo]\n" +
+ " [--multi-dex [--main-dex-list=<file> [--minimal-main-dex]]\n" +
" [<file>.class | <file>.{zip,jar,apk} | <directory>] ...\n" +
" Convert a set of classfiles into a dex file, optionally " +
"embedded in a\n" +
" jar/zip. Output name must end with one of: .dex .jar " +
- ".zip .apk. Positions\n" +
- " options: none, important, lines.\n" +
+ ".zip .apk or be a directory.\n" +
+ " Positions options: none, important, lines.\n" +
+ " --multi-dex: allows to generate several dex files if needed. " +
+ "This option is \n" +
+ " exclusive with --incremental, causes --num-threads to be ignored " +
+ "and only\n" +
+ " supports folder or archive output.\n" +
+ " --main-dex-list=<file>: <file> is a list of class file names, " +
+ "classes defined by\n" +
+ " those class files are put in classes.dex.\n" +
+ " --minimal-main-dex: only classes selected by --main-dex-list are " +
+ "to be put in\n" +
+ " the main dex.\n" +
" dx --annotool --annotation=<class> [--element=<element types>]\n" +
" [--print=<print types>]\n" +
" dx --dump [--debug] [--strict] [--bytes] [--optimize]\n" +
diff --git a/dx/src/com/android/dx/command/dexer/Main.java b/dx/src/com/android/dx/command/dexer/Main.java
index a9d5a32..ab6d2f7 100644
--- a/dx/src/com/android/dx/command/dexer/Main.java
+++ b/dx/src/com/android/dx/command/dexer/Main.java
@@ -17,11 +17,15 @@
package com.android.dx.command.dexer;
import com.android.dex.Dex;
+import com.android.dex.DexException;
import com.android.dex.DexFormat;
import com.android.dex.util.FileUtils;
import com.android.dx.Version;
import com.android.dx.cf.code.SimException;
import com.android.dx.cf.direct.ClassPathOpener;
+import com.android.dx.cf.direct.ClassPathOpener.FileNameFilter;
+import com.android.dx.cf.direct.DirectClassFile;
+import com.android.dx.cf.direct.StdAttributeFactory;
import com.android.dx.cf.iface.ParseException;
import com.android.dx.command.DxConsole;
import com.android.dx.command.UsageException;
@@ -30,6 +34,7 @@
import com.android.dx.dex.cf.CfTranslator;
import com.android.dx.dex.cf.CodeStatistics;
import com.android.dx.dex.code.PositionList;
+import com.android.dx.dex.file.AnnotationUtils;
import com.android.dx.dex.file.ClassDefItem;
import com.android.dx.dex.file.DexFile;
import com.android.dx.dex.file.EncodedMethod;
@@ -40,18 +45,24 @@
import com.android.dx.rop.annotation.AnnotationsList;
import com.android.dx.rop.cst.CstNat;
import com.android.dx.rop.cst.CstString;
+
+import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
+import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -66,6 +77,17 @@
*/
public class Main {
/**
+ * File extension of a {@code .dex} file.
+ */
+ private static final String DEX_EXTENSION = ".dex";
+
+ /**
+ * File name prefix of a {@code .dex} file automatically loaded in an
+ * archive.
+ */
+ private static final String DEX_PREFIX = "classes";
+
+ /**
* {@code non-null;} the lengthy message that tries to discourage
* people from defining core classes in applications
*/
@@ -153,6 +175,12 @@
/** class files older than this must be defined in the target dex file. */
private static long minimumFileAge = 0;
+ private static Set<String> classesInMainDex = null;
+
+ private static List<byte[]> dexOutputArrays = new ArrayList<byte[]>();
+
+ private static OutputStreamWriter humanOutWriter = null;
+
/**
* This class is uninstantiable.
*/
@@ -189,6 +217,37 @@
args = arguments;
args.makeOptionsObjects();
+ OutputStream humanOutRaw = null;
+ if (args.humanOutName != null) {
+ humanOutRaw = openOutput(args.humanOutName);
+ humanOutWriter = new OutputStreamWriter(humanOutRaw);
+ }
+
+ try {
+ if (args.multiDex) {
+ return runMultiDex();
+ } else {
+ return runMonoDex();
+ }
+ } finally {
+ closeOutput(humanOutRaw);
+ }
+ }
+
+ /**
+ * {@code non-null;} Error message for too many method/field/type ids.
+ */
+ public static String getTooManyIdsErrorMessage() {
+ if (args.multiDex) {
+ return "The list of classes given in " + Arguments.MAIN_DEX_LIST_OPTION +
+ " is too big and does not fit in the main dex.";
+ } else {
+ return "You may try using " + Arguments.MULTI_DEX_OPTION + " option.";
+ }
+ }
+
+ private static int runMonoDex() throws IOException {
+
File incrementalOutFile = null;
if (args.incremental) {
if (args.outName == null) {
@@ -231,7 +290,10 @@
// Effectively free up the (often massive) DexFile memory.
outputDex = null;
- if (!createJar(args.outName, outArray)) {
+ if (outArray != null) {
+ outputResources.put(DexFormat.DEX_IN_JAR_NAME, outArray);
+ }
+ if (!createJar(args.outName)) {
return 3;
}
} else if (outArray != null && args.outName != null) {
@@ -243,6 +305,87 @@
return 0;
}
+ private static int runMultiDex() throws IOException {
+
+ assert !args.incremental;
+ assert args.numThreads == 1;
+
+ if (args.mainDexListFile != null) {
+ classesInMainDex = loadMainDexListFile(args.mainDexListFile);
+ }
+
+ if (!processAllFiles()) {
+ return 1;
+ }
+
+ if (!libraryDexBuffers.isEmpty()) {
+ throw new DexException("Library dex files are not supported in multi-dex mode");
+ }
+
+ if (outputDex != null) {
+ // this array is null if no classes were defined
+ dexOutputArrays.add(writeDex());
+
+ // Effectively free up the (often massive) DexFile memory.
+ outputDex = null;
+ }
+
+ if (args.jarOutput) {
+
+ for (int i = 0; i < dexOutputArrays.size(); i++) {
+ outputResources.put(getDexFileName(i),
+ dexOutputArrays.get(i));
+ }
+
+ if (!createJar(args.outName)) {
+ return 3;
+ }
+ } else if (args.outName != null) {
+ File outDir = new File(args.outName);
+ assert outDir.isDirectory();
+ for (int i = 0; i < dexOutputArrays.size(); i++) {
+ OutputStream out = new FileOutputStream(new File(outDir, getDexFileName(i)));
+ try {
+ out.write(dexOutputArrays.get(i));
+ } finally {
+ closeOutput(out);
+ }
+ }
+
+ }
+
+ return 0;
+ }
+
+ private static String getDexFileName(int i) {
+ if (i == 0) {
+ return DexFormat.DEX_IN_JAR_NAME;
+ } else {
+ return DEX_PREFIX + (i + 1) + DEX_EXTENSION;
+ }
+ }
+
+ private static Set<String> loadMainDexListFile(String mainDexListFile) throws IOException {
+ Set<String> mainDexList = new HashSet<String>();
+ BufferedReader bfr = null;
+ try {
+ FileReader fr = new FileReader(mainDexListFile);
+ bfr = new BufferedReader(fr);
+
+ String line;
+
+ while (null != (line = bfr.readLine())) {
+ mainDexList.add(fixPath(line));
+ }
+
+ } finally {
+ if (bfr != null) {
+ bfr.close();
+ }
+ }
+ return mainDexList;
+ }
+
/**
* Merges the dex files {@code update} and {@code base}, preferring
* {@code update}'s definition for types defined in both dex files.
@@ -307,16 +450,12 @@
* @return whether processing was successful
*/
private static boolean processAllFiles() {
- outputDex = new DexFile(args.dexOptions);
+ createDexFile();
if (args.jarOutput) {
outputResources = new TreeMap<String, byte[]>();
}
- if (args.dumpWidth != 0) {
- outputDex.setDumpWidth(args.dumpWidth);
- }
-
anyFilesProcessed = false;
String[] fileNames = args.fileNames;
@@ -325,9 +464,40 @@
}
try {
- for (int i = 0; i < fileNames.length; i++) {
- if (processOne(fileNames[i])) {
- anyFilesProcessed = true;
+ if (args.mainDexListFile != null) {
+ // with --main-dex-list
+ FileNameFilter mainPassFilter = args.strictNameCheck ? new MainDexListFilter() :
+ new BestEffortMainDexListFilter();
+
+ // forced in main dex
+ for (int i = 0; i < fileNames.length; i++) {
+ if (processOne(fileNames[i], mainPassFilter)) {
+ anyFilesProcessed = true;
+ }
+ }
+
+ if (dexOutputArrays.size() > 1) {
+ throw new DexException("Too many classes in " + Arguments.MAIN_DEX_LIST_OPTION
+ + ", main dex capacity exceeded");
+ }
+
+ if (args.minimalMainDex) {
+ // start second pass directly in a secondary dex file.
+ createDexFile();
+ }
+
+ // remaining files
+ for (int i = 0; i < fileNames.length; i++) {
+ if (processOne(fileNames[i], new NotFilter(mainPassFilter))) {
+ anyFilesProcessed = true;
+ }
+ }
+ } else {
+ // without --main-dex-list
+ for (int i = 0; i < fileNames.length; i++) {
+ if (processOne(fileNames[i], ClassPathOpener.acceptAll)) {
+ anyFilesProcessed = true;
+ }
}
}
} catch (StopProcessing ex) {
@@ -368,18 +538,31 @@
return true;
}
+ private static void createDexFile() {
+ if (outputDex != null) {
+ dexOutputArrays.add(writeDex());
+ }
+
+ outputDex = new DexFile(args.dexOptions);
+
+ if (args.dumpWidth != 0) {
+ outputDex.setDumpWidth(args.dumpWidth);
+ }
+ }
+
/**
* Processes one pathname element.
*
* @param pathname {@code non-null;} the pathname to process. May
* be the path of a class file, a jar file, or a directory
* containing class files.
+ * @param filter {@code non-null;} A filter for excluding files.
* @return whether any processing actually happened
*/
- private static boolean processOne(String pathname) {
+ private static boolean processOne(String pathname, FileNameFilter filter) {
ClassPathOpener opener;
- opener = new ClassPathOpener(pathname, false,
+ opener = new ClassPathOpener(pathname, false, filter,
new ClassPathOpener.Consumer() {
public boolean processFileBytes(String name, long lastModified, byte[] bytes) {
if (args.numThreads > 1) {
@@ -439,6 +622,7 @@
String fixedName = fixPath(name);
if (isClass) {
+
if (keepResources && args.keepClassesInJar) {
synchronized (outputResources) {
outputResources.put(fixedName, bytes);
@@ -474,13 +658,34 @@
checkClassName(name);
}
+ DirectClassFile cf =
+ new DirectClassFile(bytes, name, args.cfOptions.strictNameCheck);
+
+ cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
+ cf.getMagic();
+
+ int numMethodIds = outputDex.getMethodIds().items().size();
+ int numFieldIds = outputDex.getFieldIds().items().size();
+ int numTypeIds = outputDex.getTypeIds().items().size();
+ int constantPoolSize = cf.getConstantPool().size();
+
+ if (args.multiDex && ((numMethodIds + constantPoolSize > args.maxNumberOfIdxPerDex) ||
+ (numFieldIds + constantPoolSize > args.maxNumberOfIdxPerDex) ||
+ (numTypeIds + constantPoolSize
+ /* annotation added by dx are not counted in numTypeIds */
+ + AnnotationUtils.DALVIK_ANNOTATION_NUMBER
+ > args.maxNumberOfIdxPerDex))) {
+ createDexFile();
+ }
+
try {
ClassDefItem clazz =
- CfTranslator.translate(name, bytes, args.cfOptions, args.dexOptions);
+ CfTranslator.translate(cf, bytes, args.cfOptions, args.dexOptions, outputDex);
synchronized (outputDex) {
outputDex.add(clazz);
}
return true;
+
} catch (ParseException ex) {
DxConsole.err.println("\ntrouble processing:");
if (args.debug) {
@@ -544,14 +749,7 @@
byte[] outArray = null;
try {
- OutputStream humanOutRaw = null;
- OutputStreamWriter humanOut = null;
try {
- if (args.humanOutName != null) {
- humanOutRaw = openOutput(args.humanOutName);
- humanOut = new OutputStreamWriter(humanOutRaw);
- }
-
if (args.methodToDump != null) {
/*
* Simply dump the requested method. Note: The call
@@ -559,23 +757,22 @@
* structures ready.
*/
outputDex.toDex(null, false);
- dumpMethod(outputDex, args.methodToDump, humanOut);
+ dumpMethod(outputDex, args.methodToDump, humanOutWriter);
} else {
/*
* This is the usual case: Create an output .dex file,
* and write it, dump it, etc.
*/
- outArray = outputDex.toDex(humanOut, args.verboseDump);
+ outArray = outputDex.toDex(humanOutWriter, args.verboseDump);
}
if (args.statistics) {
DxConsole.out.println(outputDex.getStatistics().toHuman());
}
} finally {
- if (humanOut != null) {
- humanOut.flush();
+ if (humanOutWriter != null) {
+ humanOutWriter.flush();
}
- closeOutput(humanOutRaw);
}
} catch (Exception ex) {
if (args.debug) {
@@ -592,14 +789,12 @@
}
/**
- * Creates a jar file from the resources and given dex file array.
+ * Creates a jar file from the resources (including dex file arrays).
*
* @param fileName {@code non-null;} name of the file
- * @param dexArray array containing the dex file to include, or null if the
- * output contains no class defs.
* @return whether the creation was successful
*/
- private static boolean createJar(String fileName, byte[] dexArray) {
+ private static boolean createJar(String fileName) {
/*
* Make or modify the manifest (as appropriate), put the dex
* array into the resources map, and then process the entire
@@ -611,23 +806,19 @@
OutputStream out = openOutput(fileName);
JarOutputStream jarOut = new JarOutputStream(out, manifest);
- if (dexArray != null) {
- outputResources.put(DexFormat.DEX_IN_JAR_NAME, dexArray);
- }
-
try {
for (Map.Entry<String, byte[]> e :
outputResources.entrySet()) {
String name = e.getKey();
byte[] contents = e.getValue();
JarEntry entry = new JarEntry(name);
+ int length = contents.length;
if (args.verbose) {
- DxConsole.out.println("writing " + name + "; size " +
- contents.length + "...");
+ DxConsole.out.println("writing " + name + "; size " + length + "...");
}
- entry.setSize(contents.length);
+ entry.setSize(length);
jarOut.putNextEntry(entry);
jarOut.write(contents);
jarOut.closeEntry();
@@ -856,6 +1047,84 @@
pw.flush();
}
+ private static class NotFilter implements FileNameFilter {
+ private final FileNameFilter filter;
+
+ private NotFilter(FileNameFilter filter) {
+ this.filter = filter;
+ }
+
+ @Override
+ public boolean accept(String path) {
+ return !filter.accept(path);
+ }
+ }
+
+ /**
+ * A quick and accurate filter for when file path can be trusted.
+ */
+ private static class MainDexListFilter implements FileNameFilter {
+
+ @Override
+ public boolean accept(String fullPath) {
+ if (fullPath.endsWith(".class")) {
+ String path = fixPath(fullPath);
+ return classesInMainDex.contains(path);
+ } else {
+ return true;
+ }
+ }
+ }
+
+ /**
+ * A best effort conservative filter for when file path can <b>not</b> be trusted.
+ */
+ private static class BestEffortMainDexListFilter implements FileNameFilter {
+
+ Map<String, List<String>> map = new HashMap<String, List<String>>();
+
+ public BestEffortMainDexListFilter() {
+ for (String pathOfClass : classesInMainDex) {
+ String normalized = fixPath(pathOfClass);
+ String simple = getSimpleName(normalized);
+ List<String> fullPath = map.get(simple);
+ if (fullPath == null) {
+ fullPath = new ArrayList<String>(1);
+ map.put(simple, fullPath);
+ }
+ fullPath.add(normalized);
+ }
+ }
+
+ @Override
+ public boolean accept(String path) {
+ if (path.endsWith(".class")) {
+ String normalized = fixPath(path);
+ String simple = getSimpleName(normalized);
+ List<String> fullPaths = map.get(simple);
+ if (fullPaths != null) {
+ for (String fullPath : fullPaths) {
+ if (normalized.endsWith(fullPath)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private static String getSimpleName(String path) {
+ int index = path.lastIndexOf('/');
+ if (index >= 0) {
+ return path.substring(index + 1);
+ } else {
+ return path;
+ }
+ }
+ }
+
/**
* Exception class used to halt processing prematurely.
*/
@@ -867,6 +1136,17 @@
* Command-line argument parser and access.
*/
public static class Arguments {
+
+ private static final String MINIMAL_MAIN_DEX_OPTION = "--minimal-main-dex";
+
+ private static final String MAIN_DEX_LIST_OPTION = "--main-dex-list";
+
+ private static final String MULTI_DEX_OPTION = "--multi-dex";
+
+ private static final String NUM_THREADS_OPTION = "--num-threads";
+
+ private static final String INCREMENTAL_OPTION = "--incremental";
+
/** whether to run in debug mode */
public boolean debug = false;
@@ -952,6 +1232,19 @@
/** number of threads to run with */
public int numThreads = 1;
+ /** generation of multiple dex is allowed */
+ public boolean multiDex = false;
+
+ /** Optional file containing a list of class files containing classes to be forced in main
+ * dex */
+ public String mainDexListFile = null;
+
+ /** Produce the smallest possible main dex. Ignored unless multiDex is true and
+ * mainDexListFile is specified and non empty. */
+ public boolean minimalMainDex = false;
+
+ private int maxNumberOfIdxPerDex = DexFormat.MAX_MEMBER_IDX + 1;
+
private static class ArgumentsParser {
/** The arguments to process. */
@@ -1061,6 +1354,9 @@
public void parse(String[] args) {
ArgumentsParser parser = new ArgumentsParser(args);
+ boolean outputIsDirectory = false;
+ boolean outputIsDirectDex = false;
+
while(parser.getNext()) {
if (parser.isArg("--debug")) {
debug = true;
@@ -1098,11 +1394,15 @@
keepClassesInJar = true;
} else if (parser.isArg("--output=")) {
outName = parser.getLastValue();
- if (FileUtils.hasArchiveSuffix(outName)) {
+ if (new File(outName).isDirectory()) {
+ jarOutput = false;
+ outputIsDirectory = true;
+ } else if (FileUtils.hasArchiveSuffix(outName)) {
jarOutput = true;
} else if (outName.endsWith(".dex") ||
outName.equals("-")) {
jarOutput = false;
+ outputIsDirectDex = true;
} else {
System.err.println("unknown output extension: " +
outName);
@@ -1130,13 +1430,21 @@
}
} else if (parser.isArg("--no-locals")) {
localInfo = false;
- } else if (parser.isArg("--num-threads=")) {
+ } else if (parser.isArg(NUM_THREADS_OPTION + "=")) {
numThreads = Integer.parseInt(parser.getLastValue());
- } else if (parser.isArg("--incremental")) {
+ } else if (parser.isArg(INCREMENTAL_OPTION)) {
incremental = true;
} else if (parser.isArg("--force-jumbo")) {
forceJumbo = true;
- } else {
+ } else if (parser.isArg(MULTI_DEX_OPTION)) {
+ multiDex = true;
+ } else if (parser.isArg(MAIN_DEX_LIST_OPTION + "=")) {
+ mainDexListFile = parser.getLastValue();
+ } else if (parser.isArg(MINIMAL_MAIN_DEX_OPTION)) {
+ minimalMainDex = true;
+ } else if (parser.isArg("--set-max-idx-number=")) { // undocumented test option
+ maxNumberOfIdxPerDex = Integer.parseInt(parser.getLastValue());
+ } else {
System.err.println("unknown option: " + parser.getCurrent());
throw new UsageException();
}
@@ -1156,6 +1464,39 @@
humanOutName = "-";
}
+ if (mainDexListFile != null && !multiDex) {
+ System.err.println(MAIN_DEX_LIST_OPTION + " is only supported in combination with "
+ + MULTI_DEX_OPTION);
+ throw new UsageException();
+ }
+
+ if (minimalMainDex && (mainDexListFile == null || !multiDex)) {
+ System.err.println(MINIMAL_MAIN_DEX_OPTION + " is only supported in combination with "
+ + MULTI_DEX_OPTION + " and " + MAIN_DEX_LIST_OPTION);
+ throw new UsageException();
+ }
+
+ if (multiDex && numThreads != 1) {
+ System.out.println(NUM_THREADS_OPTION + "is ignored when used with "
+ + MULTI_DEX_OPTION);
+ }
+
+ if (multiDex && incremental) {
+ System.err.println(INCREMENTAL_OPTION + " is not supported with "
+ + MULTI_DEX_OPTION);
+ throw new UsageException();
+ }
+
+ if (multiDex && outputIsDirectDex) {
+ System.err.println("Unsupported output \"" + outName +"\". " + MULTI_DEX_OPTION +
+ " supports only archive or directory output");
+ throw new UsageException();
+ }
+
+ if (outputIsDirectory && !multiDex) {
+ outName = new File(outName, DexFormat.DEX_IN_JAR_NAME).getPath();
+ }
+
makeOptionsObjects();
}
diff --git a/dx/src/com/android/dx/dex/cf/CfTranslator.java b/dx/src/com/android/dx/dex/cf/CfTranslator.java
index d4cda2a..8ab1c84 100644
--- a/dx/src/com/android/dx/dex/cf/CfTranslator.java
+++ b/dx/src/com/android/dx/dex/cf/CfTranslator.java
@@ -20,7 +20,6 @@
import com.android.dx.cf.code.ConcreteMethod;
import com.android.dx.cf.code.Ropper;
import com.android.dx.cf.direct.DirectClassFile;
-import com.android.dx.cf.direct.StdAttributeFactory;
import com.android.dx.cf.iface.Field;
import com.android.dx.cf.iface.FieldList;
import com.android.dx.cf.iface.Method;
@@ -30,8 +29,12 @@
import com.android.dx.dex.code.PositionList;
import com.android.dx.dex.code.RopTranslator;
import com.android.dx.dex.file.ClassDefItem;
+import com.android.dx.dex.file.DexFile;
import com.android.dx.dex.file.EncodedField;
import com.android.dx.dex.file.EncodedMethod;
+import com.android.dx.dex.file.FieldIdsSection;
+import com.android.dx.dex.file.MethodIdsSection;
+import com.android.dx.dex.file.TypeIdsSection;
import com.android.dx.rop.annotation.Annotations;
import com.android.dx.rop.annotation.AnnotationsList;
import com.android.dx.rop.code.AccessFlags;
@@ -41,11 +44,15 @@
import com.android.dx.rop.code.RopMethod;
import com.android.dx.rop.code.TranslationAdvice;
import com.android.dx.rop.cst.Constant;
+import com.android.dx.rop.cst.ConstantPool;
+import com.android.dx.rop.cst.CstBaseMethodRef;
import com.android.dx.rop.cst.CstBoolean;
import com.android.dx.rop.cst.CstByte;
import com.android.dx.rop.cst.CstChar;
+import com.android.dx.rop.cst.CstEnumRef;
import com.android.dx.rop.cst.CstFieldRef;
import com.android.dx.rop.cst.CstInteger;
+import com.android.dx.rop.cst.CstInterfaceMethodRef;
import com.android.dx.rop.cst.CstMethodRef;
import com.android.dx.rop.cst.CstShort;
import com.android.dx.rop.cst.CstString;
@@ -81,12 +88,12 @@
* @param dexOptions options for dex output
* @return {@code non-null;} the translated class
*/
- public static ClassDefItem translate(String filePath, byte[] bytes,
- CfOptions cfOptions, DexOptions dexOptions) {
+ public static ClassDefItem translate(DirectClassFile cf, byte[] bytes,
+ CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile) {
try {
- return translate0(filePath, bytes, cfOptions, dexOptions);
+ return translate0(cf, bytes, cfOptions, dexOptions, dexFile);
} catch (RuntimeException ex) {
- String msg = "...while processing " + filePath;
+ String msg = "...while processing " + cf.getFilePath();
throw ExceptionWithContext.withContext(ex, msg);
}
}
@@ -103,13 +110,8 @@
* @param dexOptions options for dex output
* @return {@code non-null;} the translated class
*/
- private static ClassDefItem translate0(String filePath, byte[] bytes,
- CfOptions cfOptions, DexOptions dexOptions) {
- DirectClassFile cf =
- new DirectClassFile(bytes, filePath, cfOptions.strictNameCheck);
-
- cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
- cf.getMagic();
+ private static ClassDefItem translate0(DirectClassFile cf, byte[] bytes,
+ CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile) {
OptimizerOptions.loadOptimizeLists(cfOptions.optimizeListFile,
cfOptions.dontOptimizeListFile);
@@ -130,8 +132,32 @@
out.setClassAnnotations(classAnnotations);
}
- processFields(cf, out);
- processMethods(cf, cfOptions, dexOptions, out);
+ FieldIdsSection fieldIdsSection = dexFile.getFieldIds();
+ MethodIdsSection methodIdsSection = dexFile.getMethodIds();
+ TypeIdsSection typeIdsSection = dexFile.getTypeIds();
+ processFields(cf, out, fieldIdsSection);
+ processMethods(cf, cfOptions, dexOptions, out, methodIdsSection);
+
+ // intern constant pool method, field and type references
+ ConstantPool constantPool = cf.getConstantPool();
+ int constantPoolSize = constantPool.size();
+
+ synchronized (dexFile) {
+ for (int i = 0; i < constantPoolSize; i++) {
+ Constant constant = constantPool.getOrNull(i);
+ if (constant instanceof CstMethodRef) {
+ methodIdsSection.intern((CstBaseMethodRef) constant);
+ } else if (constant instanceof CstInterfaceMethodRef) {
+ methodIdsSection.intern(((CstInterfaceMethodRef) constant).toMethodRef());
+ } else if (constant instanceof CstFieldRef) {
+ fieldIdsSection.intern((CstFieldRef) constant);
+ } else if (constant instanceof CstEnumRef) {
+ fieldIdsSection.intern(((CstEnumRef) constant).getFieldRef());
+ } else if (constant instanceof CstType) {
+ typeIdsSection.intern((CstType) constant);
+ }
+ }
+ }
return out;
}
@@ -142,7 +168,8 @@
* @param cf {@code non-null;} class being translated
* @param out {@code non-null;} output class
*/
- private static void processFields(DirectClassFile cf, ClassDefItem out) {
+ private static void processFields(
+ DirectClassFile cf, ClassDefItem out, FieldIdsSection fieldIdsSection) {
CstType thisClass = cf.getThisClass();
FieldList fields = cf.getFields();
int sz = fields.size();
@@ -170,6 +197,9 @@
if (annotations.size() != 0) {
out.addFieldAnnotations(field, annotations);
}
+ synchronized (fieldIdsSection) {
+ fieldIdsSection.intern(field);
+ }
} catch (RuntimeException ex) {
String msg = "...while processing " + one.getName().toHuman() +
" " + one.getDescriptor().toHuman();
@@ -222,7 +252,7 @@
* @param out {@code non-null;} output class
*/
private static void processMethods(DirectClassFile cf, CfOptions cfOptions,
- DexOptions dexOptions, ClassDefItem out) {
+ DexOptions dexOptions, ClassDefItem out, MethodIdsSection methodIds) {
CstType thisClass = cf.getThisClass();
MethodList methods = cf.getMethods();
int sz = methods.size();
@@ -338,6 +368,9 @@
if (list.size() != 0) {
out.addParameterAnnotations(meth, list);
}
+ synchronized (methodIds) {
+ methodIds.intern(meth);
+ }
} catch (RuntimeException ex) {
String msg = "...while processing " + one.getName().toHuman() +
" " + one.getDescriptor().toHuman();
diff --git a/dx/src/com/android/dx/dex/file/AnnotationUtils.java b/dx/src/com/android/dx/dex/file/AnnotationUtils.java
index 935c250..f7d4492 100644
--- a/dx/src/com/android/dx/dex/file/AnnotationUtils.java
+++ b/dx/src/com/android/dx/dex/file/AnnotationUtils.java
@@ -35,6 +35,13 @@
* Utility class for dealing with annotations.
*/
public final class AnnotationUtils {
+
+ /**
+ * Number of annotation types that dx may add in the dex file that were
+ * not defined in the translated class file.
+ */
+ public static final int DALVIK_ANNOTATION_NUMBER = 7;
+
/** {@code non-null;} type for {@code AnnotationDefault} annotations */
private static final CstType ANNOTATION_DEFAULT_TYPE =
CstType.intern(Type.intern("Ldalvik/annotation/AnnotationDefault;"));
diff --git a/dx/src/com/android/dx/dex/file/DexFile.java b/dx/src/com/android/dx/dex/file/DexFile.java
index 0d5b110..01a5e4b 100644
--- a/dx/src/com/android/dx/dex/file/DexFile.java
+++ b/dx/src/com/android/dx/dex/file/DexFile.java
@@ -18,7 +18,7 @@
import com.android.dex.util.ExceptionWithContext;
import com.android.dx.dex.DexOptions;
-import static com.android.dx.dex.file.MixedItemSection.SortType;
+import com.android.dx.dex.file.MixedItemSection.SortType;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstBaseMethodRef;
import com.android.dx.rop.cst.CstEnumRef;
@@ -27,6 +27,7 @@
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.type.Type;
import com.android.dx.util.ByteArrayAnnotatedOutput;
+
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
@@ -241,7 +242,7 @@
* @return {@code >= 0;} the total file size
* @throws RuntimeException thrown if the file size is not yet known
*/
- /*package*/ int getFileSize() {
+ public int getFileSize() {
if (fileSize < 0) {
throw new RuntimeException("file size not yet known");
}
@@ -342,13 +343,13 @@
/**
* Gets the type identifiers section.
*
- * <p>This is package-scope in order to allow
+ * <p>This is public in order to allow
* the various {@link Item} instances to add items to the
- * instance.</p>
+ * instance and help early counting of type ids.</p>
*
* @return {@code non-null;} the class identifiers section
*/
- /*package*/ TypeIdsSection getTypeIds() {
+ public TypeIdsSection getTypeIds() {
return typeIds;
}
@@ -368,26 +369,26 @@
/**
* Gets the field identifiers section.
*
- * <p>This is package-scope in order to allow
+ * <p>This is public in order to allow
* the various {@link Item} instances to add items to the
- * instance.</p>
+ * instance and help early counting of field ids.</p>
*
* @return {@code non-null;} the field identifiers section
*/
- /*package*/ FieldIdsSection getFieldIds() {
+ public FieldIdsSection getFieldIds() {
return fieldIds;
}
/**
* Gets the method identifiers section.
*
- * <p>This is package-scope in order to allow
+ * <p>This is public in order to allow
* the various {@link Item} instances to add items to the
- * instance.</p>
+ * instance and help early counting of method ids.</p>
*
* @return {@code non-null;} the method identifiers section
*/
- /*package*/ MethodIdsSection getMethodIds() {
+ public MethodIdsSection getMethodIds() {
return methodIds;
}
diff --git a/dx/src/com/android/dx/dex/file/MemberIdsSection.java b/dx/src/com/android/dx/dex/file/MemberIdsSection.java
index 9f82f41..bd15df7 100644
--- a/dx/src/com/android/dx/dex/file/MemberIdsSection.java
+++ b/dx/src/com/android/dx/dex/file/MemberIdsSection.java
@@ -17,6 +17,9 @@
package com.android.dx.dex.file;
import com.android.dex.DexException;
+import com.android.dex.DexFormat;
+import com.android.dx.command.dexer.Main;
+
import java.util.Formatter;
import java.util.Map;
import java.util.TreeMap;
@@ -26,8 +29,6 @@
* Member (field or method) refs list section of a {@code .dex} file.
*/
public abstract class MemberIdsSection extends UniformItemSection {
- /** The largest addressable member is 0xffff, in the dex spec as field@CCCC or meth@CCCC. */
- private static final int MAX_MEMBERS = 0x10000;
/**
* Constructs an instance. The file offset is initially unknown.
@@ -45,8 +46,8 @@
protected void orderItems() {
int idx = 0;
- if (items().size() > MAX_MEMBERS) {
- throw new DexException(tooManyMembersMessage());
+ if (items().size() > DexFormat.MAX_MEMBER_IDX + 1) {
+ throw new DexException(getTooManyMembersMessage());
}
for (Object i : items()) {
@@ -55,7 +56,7 @@
}
}
- private String tooManyMembersMessage() {
+ private String getTooManyMembersMessage() {
Map<String, AtomicInteger> membersByPackage = new TreeMap<String, AtomicInteger>();
for (Object member : items()) {
String packageName = ((MemberIdItem) member).getDefiningClass().getPackageName();
@@ -68,12 +69,19 @@
}
Formatter formatter = new Formatter();
- String memberType = this instanceof MethodIdsSection ? "methods" : "fields";
- formatter.format("Too many %s: %d; max is %d. By package:",
- memberType, items().size(), MAX_MEMBERS);
- for (Map.Entry<String, AtomicInteger> entry : membersByPackage.entrySet()) {
- formatter.format("%n%6d %s", entry.getValue().get(), entry.getKey());
+ try {
+ String memberType = this instanceof MethodIdsSection ? "method" : "field";
+ formatter.format("Too many %s references: %d; max is %d.%n" +
+ Main.getTooManyIdsErrorMessage() + "%n" +
+ "References by package:",
+ memberType, items().size(), DexFormat.MAX_MEMBER_IDX + 1);
+ for (Map.Entry<String, AtomicInteger> entry : membersByPackage.entrySet()) {
+ formatter.format("%n%6d %s", entry.getValue().get(), entry.getKey());
+ }
+ return formatter.toString();
+ } finally {
+ formatter.close();
}
- return formatter.toString();
}
+
}
diff --git a/dx/src/com/android/dx/dex/file/TypeIdsSection.java b/dx/src/com/android/dx/dex/file/TypeIdsSection.java
index 1df4b46..ef47262 100644
--- a/dx/src/com/android/dx/dex/file/TypeIdsSection.java
+++ b/dx/src/com/android/dx/dex/file/TypeIdsSection.java
@@ -16,11 +16,15 @@
package com.android.dx.dex.file;
+import com.android.dex.DexException;
+import com.android.dex.DexFormat;
+import com.android.dx.command.dexer.Main;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.type.Type;
import com.android.dx.util.AnnotatedOutput;
import com.android.dx.util.Hex;
+
import java.util.Collection;
import java.util.TreeMap;
@@ -80,8 +84,10 @@
int sz = typeIds.size();
int offset = (sz == 0) ? 0 : getFileOffset();
- if (sz > 65536) {
- throw new UnsupportedOperationException("too many type ids");
+ if (sz > DexFormat.MAX_TYPE_IDX + 1) {
+ throw new DexException("Too many type references: " + sz +
+ "; max is " + (DexFormat.MAX_TYPE_IDX + 1) + ".\n" +
+ Main.getTooManyIdsErrorMessage());
}
if (out.annotates()) {
diff --git a/libdex/Android.mk b/libdex/Android.mk
index 48263c9..7ab0013 100644
--- a/libdex/Android.mk
+++ b/libdex/Android.mk
@@ -50,6 +50,7 @@
#LOCAL_CFLAGS += -UNDEBUG -DDEBUG=1
LOCAL_SRC_FILES := $(dex_src_files)
LOCAL_C_INCLUDES += $(dex_include_files)
+LOCAL_STATIC_LIBRARIES := liblog
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := libdex
include $(BUILD_STATIC_LIBRARY)
@@ -65,6 +66,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(dex_src_files)
LOCAL_C_INCLUDES += $(dex_include_files)
+LOCAL_STATIC_LIBRARIES := liblog
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := libdex
include $(BUILD_HOST_STATIC_LIBRARY)
diff --git a/libdex/DexSwapVerify.cpp b/libdex/DexSwapVerify.cpp
index 6cc139d..ff47ab5 100644
--- a/libdex/DexSwapVerify.cpp
+++ b/libdex/DexSwapVerify.cpp
@@ -912,8 +912,9 @@
SWAP_OFFSET4(item->classDataOff);
if ((item->accessFlags & ~ACC_CLASS_MASK) != 0) {
- ALOGE("Bogus class access flags %x", item->accessFlags);
- return NULL;
+ // The VM specification says that unknown flags should be ignored.
+ ALOGV("Bogus class access flags %x", item->accessFlags);
+ item->accessFlags &= ACC_CLASS_MASK;
}
return item + 1;
@@ -1457,8 +1458,9 @@
}
if ((accessFlags & ~ACC_FIELD_MASK) != 0) {
- ALOGE("Bogus field access flags %x @ %d", accessFlags, i);
- return false;
+ // The VM specification says that unknown flags should be ignored.
+ ALOGV("Bogus field access flags %x @ %d", accessFlags, i);
+ field->accessFlags &= ACC_FIELD_MASK;
}
}
@@ -1487,12 +1489,17 @@
return false;
}
- if (((accessFlags & ~ACC_METHOD_MASK) != 0)
- || (isSynchronized && !allowSynchronized)) {
- ALOGE("Bogus method access flags %x @ %d", accessFlags, i);
+ if (isSynchronized && !allowSynchronized) {
+ ALOGE("Bogus method access flags (synchronization) %x @ %d", accessFlags, i);
return false;
}
+ if ((accessFlags & ~ACC_METHOD_MASK) != 0) {
+ // The VM specification says that unknown flags should be ignored.
+ ALOGV("Bogus method access flags %x @ %d", accessFlags, i);
+ method->accessFlags &= ACC_METHOD_MASK;
+ }
+
if (expectCode) {
if (method->codeOff == 0) {
ALOGE("Unexpected zero code_off for access_flags %x",
diff --git a/libdex/ZipArchive.cpp b/libdex/ZipArchive.cpp
index 1cccc3f..f70a5df 100644
--- a/libdex/ZipArchive.cpp
+++ b/libdex/ZipArchive.cpp
@@ -28,6 +28,7 @@
#include <errno.h>
#include <JNIHelp.h> // TEMP_FAILURE_RETRY may or may not be in unistd
+#include <utils/Compat.h> // For off64_t and lseek64 on Mac
#ifndef O_BINARY
#define O_BINARY 0
@@ -36,37 +37,47 @@
/*
* Zip file constants.
*/
-#define kEOCDSignature 0x06054b50
-#define kEOCDLen 22
-#define kEOCDNumEntries 8 // offset to #of entries in file
-#define kEOCDSize 12 // size of the central directory
-#define kEOCDFileOffset 16 // offset to central directory
+#define kEOCDSignature 0x06054b50
+#define kEOCDLen 22
+#define kEOCDDiskNumber 4 // number of the current disk
+#define kEOCDDiskNumberForCD 6 // disk number with the Central Directory
+#define kEOCDNumEntries 8 // offset to #of entries in file
+#define kEOCDTotalNumEntries 10 // offset to total #of entries in spanned archives
+#define kEOCDSize 12 // size of the central directory
+#define kEOCDFileOffset 16 // offset to central directory
+#define kEOCDCommentSize 20 // offset to the length of the file comment
-#define kMaxCommentLen 65535 // longest possible in ushort
-#define kMaxEOCDSearch (kMaxCommentLen + kEOCDLen)
+#define kMaxCommentLen 65535 // longest possible in ushort
+#define kMaxEOCDSearch (kMaxCommentLen + kEOCDLen)
-#define kLFHSignature 0x04034b50
-#define kLFHLen 30 // excluding variable-len fields
-#define kLFHNameLen 26 // offset to filename length
-#define kLFHExtraLen 28 // offset to extra length
+#define kLFHSignature 0x04034b50
+#define kLFHLen 30 // excluding variable-len fields
+#define kLFHGPBFlags 6 // offset to GPB flags
+#define kLFHNameLen 26 // offset to filename length
+#define kLFHExtraLen 28 // offset to extra length
-#define kCDESignature 0x02014b50
-#define kCDELen 46 // excluding variable-len fields
-#define kCDEMethod 10 // offset to compression method
-#define kCDEModWhen 12 // offset to modification timestamp
-#define kCDECRC 16 // offset to entry CRC
-#define kCDECompLen 20 // offset to compressed length
-#define kCDEUncompLen 24 // offset to uncompressed length
-#define kCDENameLen 28 // offset to filename length
-#define kCDEExtraLen 30 // offset to extra length
-#define kCDECommentLen 32 // offset to comment length
-#define kCDELocalOffset 42 // offset to local hdr
+#define kCDESignature 0x02014b50
+#define kCDELen 46 // excluding variable-len fields
+#define kCDEGPBFlags 8 // offset to GPB flags
+#define kCDEMethod 10 // offset to compression method
+#define kCDEModWhen 12 // offset to modification timestamp
+#define kCDECRC 16 // offset to entry CRC
+#define kCDECompLen 20 // offset to compressed length
+#define kCDEUncompLen 24 // offset to uncompressed length
+#define kCDENameLen 28 // offset to filename length
+#define kCDEExtraLen 30 // offset to extra length
+#define kCDECommentLen 32 // offset to comment length
+#define kCDELocalOffset 42 // offset to local hdr
+
+/* General Purpose Bit Flag */
+#define kGPFEncryptedFlag (1 << 0)
+#define kGPFUnsupportedMask (kGPFEncryptedFlag)
/*
- * The values we return for ZipEntry use 0 as an invalid value, so we
+ * The values we return for ZipEntryRO use 0 as an invalid value, so we
* want to adjust the hash table index by a fixed amount. Using a large
- * value helps insure that people don't mix & match arguments, e.g. with
- * entry indices.
+ * value helps insure that people don't mix & match arguments, e.g. to
+ * findEntryByIndex().
*/
#define kZipEntryAdj 10000
@@ -142,20 +153,54 @@
}
static int mapCentralDirectory0(int fd, const char* debugFileName,
- ZipArchive* pArchive, off_t fileLength, size_t readAmount, u1* scanBuf)
+ ZipArchive* pArchive, off64_t fileLength, size_t readAmount, u1* scanBuf)
{
- off_t searchStart = fileLength - readAmount;
+ /*
+ * Make sure this is a Zip archive.
+ */
+ if (lseek64(pArchive->mFd, 0, SEEK_SET) != 0) {
+ ALOGW("seek to start failed: %s", strerror(errno));
+ return false;
+ }
- if (lseek(fd, searchStart, SEEK_SET) != searchStart) {
- ALOGW("Zip: seek %ld failed: %s", (long) searchStart, strerror(errno));
- return -1;
+ ssize_t actual = TEMP_FAILURE_RETRY(read(pArchive->mFd, scanBuf, sizeof(int32_t)));
+ if (actual != (ssize_t) sizeof(int32_t)) {
+ ALOGI("couldn't read first signature from zip archive: %s", strerror(errno));
+ return false;
}
- ssize_t actual = TEMP_FAILURE_RETRY(read(fd, scanBuf, readAmount));
+
+ unsigned int header = get4LE(scanBuf);
+ if (header != kLFHSignature) {
+ ALOGV("Not a Zip archive (found 0x%08x)\n", header);
+ return false;
+ }
+
+ /*
+ * Perform the traditional EOCD snipe hunt.
+ *
+ * We're searching for the End of Central Directory magic number,
+ * which appears at the start of the EOCD block. It's followed by
+ * 18 bytes of EOCD stuff and up to 64KB of archive comment. We
+ * need to read the last part of the file into a buffer, dig through
+ * it to find the magic number, parse some values out, and use those
+ * to determine the extent of the CD.
+ *
+ * We start by pulling in the last part of the file.
+ */
+ off64_t searchStart = fileLength - readAmount;
+
+ if (lseek64(pArchive->mFd, searchStart, SEEK_SET) != searchStart) {
+ ALOGW("seek %ld failed: %s\n", (long) searchStart, strerror(errno));
+ return false;
+ }
+ actual = TEMP_FAILURE_RETRY(read(pArchive->mFd, scanBuf, readAmount));
if (actual != (ssize_t) readAmount) {
- ALOGW("Zip: read %zd failed: %s", readAmount, strerror(errno));
- return -1;
+ ALOGW("Zip: read %zd, expected %zd. Failed: %s\n",
+ actual, readAmount, strerror(errno));
+ return false;
}
+
/*
* Scan backward for the EOCD magic. In an archive without a trailing
* comment, we'll find it on the first try. (We may want to consider
@@ -174,7 +219,7 @@
return -1;
}
- off_t eocdOffset = searchStart + i;
+ off64_t eocdOffset = searchStart + i;
const u1* eocdPtr = scanBuf + i;
assert(eocdOffset < fileLength);
@@ -183,28 +228,43 @@
* Grab the CD offset and size, and the number of entries in the
* archive. Verify that they look reasonable.
*/
+ u4 diskNumber = get2LE(eocdPtr + kEOCDDiskNumber);
+ u4 diskWithCentralDir = get2LE(eocdPtr + kEOCDDiskNumberForCD);
u4 numEntries = get2LE(eocdPtr + kEOCDNumEntries);
- u4 dirSize = get4LE(eocdPtr + kEOCDSize);
- u4 dirOffset = get4LE(eocdPtr + kEOCDFileOffset);
+ u4 totalNumEntries = get2LE(eocdPtr + kEOCDTotalNumEntries);
+ u4 centralDirSize = get4LE(eocdPtr + kEOCDSize);
+ u4 centralDirOffset = get4LE(eocdPtr + kEOCDFileOffset);
+ u4 commentSize = get2LE(eocdPtr + kEOCDCommentSize);
- if ((long long) dirOffset + (long long) dirSize > (long long) eocdOffset) {
- ALOGW("Zip: bad offsets (dir %ld, size %u, eocd %ld)",
- (long) dirOffset, dirSize, (long) eocdOffset);
- return -1;
+ // Verify that they look reasonable.
+ if ((long long) centralDirOffset + (long long) centralDirSize > (long long) eocdOffset) {
+ ALOGW("bad offsets (dir %ld, size %u, eocd %ld)\n",
+ (long) centralDirOffset, centralDirSize, (long) eocdOffset);
+ return false;
}
if (numEntries == 0) {
- ALOGW("Zip: empty archive?");
- return -1;
+ ALOGW("empty archive?\n");
+ return false;
+ } else if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) {
+ ALOGW("spanned archives not supported");
+ return false;
}
- ALOGV("+++ numEntries=%d dirSize=%d dirOffset=%d",
- numEntries, dirSize, dirOffset);
+ // Check to see if comment is a sane size
+ if (((size_t) commentSize > (fileLength - kEOCDLen))
+ || (eocdOffset > (fileLength - kEOCDLen) - commentSize)) {
+ ALOGW("comment size runs off end of file");
+ return false;
+ }
+
+ ALOGV("+++ numEntries=%d dirSize=%d dirOffset=%d\n",
+ numEntries, centralDirSize, centralDirOffset);
/*
* It all looks good. Create a mapping for the CD, and set the fields
* in pArchive.
*/
- if (sysMapFileSegmentInShmem(fd, dirOffset, dirSize,
+ if (sysMapFileSegmentInShmem(fd, centralDirOffset, centralDirSize,
&pArchive->mDirectoryMap) != 0)
{
ALOGW("Zip: cd map failed");
@@ -212,7 +272,7 @@
}
pArchive->mNumEntries = numEntries;
- pArchive->mDirectoryOffset = dirOffset;
+ pArchive->mDirectoryOffset = centralDirOffset;
return 0;
}
@@ -231,7 +291,7 @@
/*
* Get and test file length.
*/
- off_t fileLength = lseek(fd, 0, SEEK_END);
+ off64_t fileLength = lseek64(fd, 0, SEEK_END);
if (fileLength < kEOCDLen) {
ALOGV("Zip: length %ld is too small to be zip", (long) fileLength);
return -1;
@@ -309,16 +369,31 @@
goto bail;
}
- unsigned int fileNameLen, extraLen, commentLen, hash;
- fileNameLen = get2LE(ptr + kCDENameLen);
+ unsigned int gpbf = get2LE(ptr + kCDEGPBFlags);
+ if ((gpbf & kGPFUnsupportedMask) != 0) {
+ ALOGW("Invalid General Purpose Bit Flag: %d", gpbf);
+ goto bail;
+ }
+
+ unsigned int nameLen, extraLen, commentLen, hash;
+ nameLen = get2LE(ptr + kCDENameLen);
extraLen = get2LE(ptr + kCDEExtraLen);
commentLen = get2LE(ptr + kCDECommentLen);
- /* add the CDE filename to the hash table */
- hash = computeHash((const char*)ptr + kCDELen, fileNameLen);
- addToHash(pArchive, (const char*)ptr + kCDELen, fileNameLen, hash);
+ const char *name = (const char *) ptr + kCDELen;
- ptr += kCDELen + fileNameLen + extraLen + commentLen;
+ /* Check name for NULL characters */
+ if (memchr(name, 0, nameLen) != NULL) {
+ ALOGW("Filename contains NUL byte");
+ goto bail;
+ }
+
+ /* add the CDE filename to the hash table */
+ hash = computeHash(name, nameLen);
+ addToHash(pArchive, name, nameLen, hash);
+
+ /* We don't care about the comment or extra data. */
+ ptr += kCDELen + nameLen + extraLen + commentLen;
if ((size_t)(ptr - cdPtr) > cdLength) {
ALOGW("Zip: bad CD advance (%d vs %zd) at entry %d",
(int) (ptr - cdPtr), cdLength, i);
@@ -553,7 +628,13 @@
return -1;
}
- off_t dataOffset = localHdrOffset + kLFHLen
+ u4 gpbf = get2LE(lfhBuf + kLFHGPBFlags);
+ if ((gpbf & kGPFUnsupportedMask) != 0) {
+ ALOGW("Invalid General Purpose Bit Flag: %d", gpbf);
+ return -1;
+ }
+
+ off64_t dataOffset = localHdrOffset + kLFHLen
+ get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen);
if (dataOffset >= cdOffset) {
ALOGW("Zip: bad data offset %ld in zip", (long) dataOffset);
diff --git a/tests/089-many-methods/expected.txt b/tests/089-many-methods/expected.txt
index fc97b3d..b74e0ee 100644
--- a/tests/089-many-methods/expected.txt
+++ b/tests/089-many-methods/expected.txt
@@ -1,4 +1,6 @@
-trouble writing output: Too many fields: 131000; max is 65536. By package:
+trouble writing output: Too many field references: 131000; max is 65536.
+You may try using --multi-dex option.
+References by package:
131000 default
build exit status: 2
diff --git a/tests/097-suspend-check/expected.txt b/tests/097-suspend-check/expected.txt
new file mode 100644
index 0000000..07cc825
--- /dev/null
+++ b/tests/097-suspend-check/expected.txt
@@ -0,0 +1,7 @@
+Running (5 seconds) ...
+.
+.
+.
+.
+.
+Done.
diff --git a/tests/097-suspend-check/info.txt b/tests/097-suspend-check/info.txt
new file mode 100644
index 0000000..d89d66a
--- /dev/null
+++ b/tests/097-suspend-check/info.txt
@@ -0,0 +1,2 @@
+To support garbage collection, debugging and profiling the VM must be able to send all threads
+to a safepoint. This tests the ability of the VM to do this for a tight loop.
diff --git a/tests/097-suspend-check/src/Main.java b/tests/097-suspend-check/src/Main.java
new file mode 100644
index 0000000..d92b9e5
--- /dev/null
+++ b/tests/097-suspend-check/src/Main.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+public class Main {
+ private static final int TEST_TIME = 5;
+
+ public static void main(String[] args) {
+ System.out.println("Running (" + TEST_TIME + " seconds) ...");
+ InfiniteForLoop forLoop = new InfiniteForLoop();
+ InfiniteWhileLoop whileLoop = new InfiniteWhileLoop();
+ InfiniteDoWhileLoop doWhileLoop = new InfiniteDoWhileLoop();
+ MakeGarbage garbage = new MakeGarbage();
+ forLoop.start();
+ whileLoop.start();
+ doWhileLoop.start();
+ garbage.start();
+ for (int i = 0; i < TEST_TIME; i++) {
+ System.gc();
+ System.out.println(".");
+ sleep(1000);
+ }
+ forLoop.stopNow();
+ whileLoop.stopNow();
+ doWhileLoop.stopNow();
+ garbage.stopNow();
+ System.out.println("Done.");
+ }
+
+ public static void sleep(int ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException ie) {
+ System.err.println("sleep was interrupted");
+ }
+ }
+}
+
+class InfiniteWhileLoop extends Thread {
+ volatile private boolean keepGoing = true;
+ public void run() {
+ int i = 0;
+ while (keepGoing) {
+ i++;
+ }
+ }
+ public void stopNow() {
+ keepGoing = false;
+ }
+}
+
+class InfiniteDoWhileLoop extends Thread {
+ volatile private boolean keepGoing = true;
+ public void run() {
+ int i = 0;
+ do {
+ i++;
+ } while (keepGoing);
+ }
+ public void stopNow() {
+ keepGoing = false;
+ }
+}
+
+class InfiniteForLoop extends Thread {
+ int count = 100000;
+ volatile private boolean keepGoing = true;
+ public void run() {
+ int i = 0;
+ for (int j = 0; keepGoing; j++) {
+ i += j;
+ }
+ }
+ public void stopNow() {
+ keepGoing = false;
+ }
+}
+
+
+class MakeGarbage extends Thread {
+ volatile private boolean keepGoing = true;
+ public void run() {
+ while (keepGoing) {
+ byte[] garbage = new byte[100000];
+ }
+ }
+ public void stopNow() {
+ keepGoing = false;
+ }
+}
diff --git a/tests/098-native-allocations/expected.txt b/tests/098-native-allocations/expected.txt
new file mode 100644
index 0000000..f75da10
--- /dev/null
+++ b/tests/098-native-allocations/expected.txt
@@ -0,0 +1 @@
+Test complete
diff --git a/tests/098-native-allocations/info.txt b/tests/098-native-allocations/info.txt
new file mode 100644
index 0000000..2e5b88a
--- /dev/null
+++ b/tests/098-native-allocations/info.txt
@@ -0,0 +1,2 @@
+This is a test to verify that native allocation successfully runs
+finalizers and prevents OOM.
diff --git a/tests/098-native-allocations/src/Main.java b/tests/098-native-allocations/src/Main.java
new file mode 100644
index 0000000..87179b5
--- /dev/null
+++ b/tests/098-native-allocations/src/Main.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+import java.lang.reflect.*;
+
+class Main {
+ static Object nativeLock = new Object();
+ static int nativeBytes = 0;
+ static Object runtime;
+ static Method register_native_allocation;
+ static Method register_native_free;
+ static int maxMem = 64 * 1024 * 1024;
+
+ static class NativeAllocation {
+ private int bytes;
+
+ NativeAllocation(int bytes) throws Exception {
+ this.bytes = bytes;
+ register_native_allocation.invoke(runtime, bytes);
+ synchronized (nativeLock) {
+ nativeBytes += bytes;
+ if (nativeBytes > maxMem) {
+ throw new OutOfMemoryError();
+ }
+ }
+ }
+
+ protected void finalize() throws Exception {
+ synchronized (nativeLock) {
+ nativeBytes -= bytes;
+ }
+ register_native_free.invoke(runtime, bytes);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ Class<?> vm_runtime = Class.forName("dalvik.system.VMRuntime");
+ Method get_runtime = vm_runtime.getDeclaredMethod("getRuntime");
+ runtime = get_runtime.invoke(null);
+ register_native_allocation = vm_runtime.getDeclaredMethod("registerNativeAllocation", Integer.TYPE);
+ register_native_free = vm_runtime.getDeclaredMethod("registerNativeFree", Integer.TYPE);
+ int count = 16;
+ int size = 512 * 0x400;
+ int allocation_count = 256;
+ NativeAllocation[] allocations = new NativeAllocation[count];
+ for (int i = 0; i < allocation_count; ++i) {
+ allocations[i % count] = new NativeAllocation(size);
+ }
+ System.out.println("Test complete");
+ }
+}
+
diff --git a/tools/jdwpspy/Android.mk b/tools/jdwpspy/Android.mk
deleted file mode 100644
index 2201aab..0000000
--- a/tools/jdwpspy/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2006 The Android Open Source Project
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- Main.cpp \
- Net.cpp \
- find_JdwpConstants.cpp
-
-LOCAL_C_INCLUDES += \
- dalvik/vm
-
-LOCAL_MODULE := jdwpspy
-
-include $(BUILD_HOST_EXECUTABLE)
-
diff --git a/tools/jdwpspy/Common.h b/tools/jdwpspy/Common.h
deleted file mode 100644
index ddaba9c..0000000
--- a/tools/jdwpspy/Common.h
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2006 The Android Open Source Project
- *
- * jdwpspy common stuff.
- */
-#ifndef _JDWPSPY_COMMON
-#define _JDWPSPY_COMMON
-
-#include <stdio.h>
-#include <sys/types.h>
-
-typedef unsigned char u1;
-typedef unsigned short u2;
-typedef unsigned int u4;
-typedef unsigned long long u8;
-
-#define NELEM(x) (sizeof(x) / sizeof((x)[0]))
-
-#ifndef _JDWP_MISC_INLINE
-# define INLINE extern inline
-#else
-# define INLINE
-#endif
-
-/*
- * Get 1 byte. (Included to make the code more legible.)
- */
-INLINE u1 get1(unsigned const char* pSrc)
-{
- return *pSrc;
-}
-
-/*
- * Get 2 big-endian bytes.
- */
-INLINE u2 get2BE(unsigned char const* pSrc)
-{
- u2 result;
-
- result = *pSrc++ << 8;
- result |= *pSrc++;
-
- return result;
-}
-
-/*
- * Get 4 big-endian bytes.
- */
-INLINE u4 get4BE(unsigned char const* pSrc)
-{
- u4 result;
-
- result = *pSrc++ << 24;
- result |= *pSrc++ << 16;
- result |= *pSrc++ << 8;
- result |= *pSrc++;
-
- return result;
-}
-
-/*
- * Get 8 big-endian bytes.
- */
-INLINE u8 get8BE(unsigned char const* pSrc)
-{
- u8 result;
-
- result = (u8) *pSrc++ << 56;
- result |= (u8) *pSrc++ << 48;
- result |= (u8) *pSrc++ << 40;
- result |= (u8) *pSrc++ << 32;
- result |= (u8) *pSrc++ << 24;
- result |= (u8) *pSrc++ << 16;
- result |= (u8) *pSrc++ << 8;
- result |= (u8) *pSrc++;
-
- return result;
-}
-
-
-/*
- * Start here.
- */
-int run(const char* connectHost, int connectPort, int listenPort);
-
-/*
- * Print a hex dump to the specified file pointer.
- *
- * "local" mode prints a hex dump starting from offset 0 (roughly equivalent
- * to "xxd -g1").
- *
- * "mem" mode shows the actual memory address, and will offset the start
- * so that the low nibble of the address is always zero.
- */
-typedef enum { kHexDumpLocal, kHexDumpMem } HexDumpMode;
-void printHexDump(const void* vaddr, size_t length);
-void printHexDump2(const void* vaddr, size_t length, const char* prefix);
-void printHexDumpEx(FILE* fp, const void* vaddr, size_t length,
- HexDumpMode mode, const char* prefix);
-
-#endif /*_JDWPSPY_COMMON*/
diff --git a/tools/jdwpspy/Main.cpp b/tools/jdwpspy/Main.cpp
deleted file mode 100644
index b53f99b..0000000
--- a/tools/jdwpspy/Main.cpp
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright 2006 The Android Open Source Project
- *
- * JDWP spy.
- */
-#define _JDWP_MISC_INLINE
-#include "Common.h"
-#include <stdlib.h>
-#include <stdio.h>
-#include <stdint.h>
-#include <string.h>
-#include <assert.h>
-#include <ctype.h>
-
-static const char gHexDigit[] = "0123456789abcdef";
-
-/*
- * Print a hex dump. Just hands control off to the fancy version.
- */
-void printHexDump(const void* vaddr, size_t length)
-{
- printHexDumpEx(stdout, vaddr, length, kHexDumpLocal, "");
-}
-void printHexDump2(const void* vaddr, size_t length, const char* prefix)
-{
- printHexDumpEx(stdout, vaddr, length, kHexDumpLocal, prefix);
-}
-
-/*
- * Print a hex dump in this format:
- *
-01234567: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef\n
- */
-void printHexDumpEx(FILE* fp, const void* vaddr, size_t length,
- HexDumpMode mode, const char* prefix)
-{
- const unsigned char* addr = reinterpret_cast<const unsigned char*>(vaddr);
- char out[77]; /* exact fit */
- uintptr_t offset; /* offset to show while printing */
- char* hex;
- char* asc;
- int gap;
-
- if (mode == kHexDumpLocal)
- offset = 0;
- else
- offset = (uintptr_t) addr;
-
- memset(out, ' ', sizeof(out)-1);
- out[8] = ':';
- out[sizeof(out)-2] = '\n';
- out[sizeof(out)-1] = '\0';
-
- gap = (int) offset & 0x0f;
- while (length) {
- unsigned int lineOffset = offset & ~0x0f;
- char* hex = out;
- char* asc = out + 59;
-
- for (int i = 0; i < 8; i++) {
- *hex++ = gHexDigit[lineOffset >> 28];
- lineOffset <<= 4;
- }
- hex++;
- hex++;
-
- int count = ((int)length > 16-gap) ? 16-gap : (int) length; /* cap length */
- assert(count != 0);
- assert(count+gap <= 16);
-
- if (gap) {
- /* only on first line */
- hex += gap * 3;
- asc += gap;
- }
-
- int i;
- for (i = gap ; i < count+gap; i++) {
- *hex++ = gHexDigit[*addr >> 4];
- *hex++ = gHexDigit[*addr & 0x0f];
- hex++;
- if (isprint(*addr))
- *asc++ = *addr;
- else
- *asc++ = '.';
- addr++;
- }
- for ( ; i < 16; i++) {
- /* erase extra stuff; only happens on last line */
- *hex++ = ' ';
- *hex++ = ' ';
- hex++;
- *asc++ = ' ';
- }
-
- fprintf(fp, "%s%s", prefix, out);
-
- gap = 0;
- length -= count;
- offset += count;
- }
-}
-
-
-/*
- * Explain it.
- */
-static void usage(const char* progName)
-{
- fprintf(stderr, "Usage: %s VM-port [debugger-listen-port]\n\n", progName);
- fprintf(stderr,
-"When a debugger connects to the debugger-listen-port, jdwpspy will connect\n");
- fprintf(stderr, "to the VM on the VM-port.\n");
-}
-
-/*
- * Parse args.
- */
-int main(int argc, char* argv[])
-{
- if (argc < 2 || argc > 3) {
- usage("jdwpspy");
- return 2;
- }
-
- setvbuf(stdout, NULL, _IONBF, 0);
-
- /* may want this to be host:port */
- int connectPort = atoi(argv[1]);
-
- int listenPort;
- if (argc > 2)
- listenPort = atoi(argv[2]);
- else
- listenPort = connectPort + 1;
-
- int cc = run("localhost", connectPort, listenPort);
-
- return (cc != 0);
-}
diff --git a/tools/jdwpspy/Net.cpp b/tools/jdwpspy/Net.cpp
deleted file mode 100644
index b923006..0000000
--- a/tools/jdwpspy/Net.cpp
+++ /dev/null
@@ -1,748 +0,0 @@
-/*
- * Copyright 2006 The Android Open Source Project
- *
- * JDWP spy. This is a rearranged version of the JDWP code from the VM.
- */
-#include "Common.h"
-#include "jdwp/JdwpConstants.h"
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <arpa/inet.h>
-#include <netdb.h>
-#include <time.h>
-#include <errno.h>
-#include <assert.h>
-
-#define kInputBufferSize (256*1024)
-
-#define kMagicHandshakeLen 14 /* "JDWP-Handshake" */
-#define kJDWPHeaderLen 11
-#define kJDWPFlagReply 0x80
-
-
-/*
- * Information about the remote end.
- */
-typedef struct Peer {
- char label[2]; /* 'D' or 'V' */
-
- int sock;
- unsigned char inputBuffer[kInputBufferSize];
- int inputCount;
-
- bool awaitingHandshake; /* waiting for "JDWP-Handshake" */
-} Peer;
-
-
-/*
- * Network state.
- */
-typedef struct NetState {
- /* listen here for connection from debugger */
- int listenSock;
-
- /* connect here to contact VM */
- struct in_addr vmAddr;
- short vmPort;
-
- Peer dbg;
- Peer vm;
-} NetState;
-
-/*
- * Function names.
- */
-typedef struct {
- u1 cmdSet;
- u1 cmd;
- const char* descr;
-} JdwpHandlerMap;
-
-/*
- * Map commands to names.
- *
- * Command sets 0-63 are incoming requests, 64-127 are outbound requests,
- * and 128-256 are vendor-defined.
- */
-static const JdwpHandlerMap gHandlerMap[] = {
- /* VirtualMachine command set (1) */
- { 1, 1, "VirtualMachine.Version" },
- { 1, 2, "VirtualMachine.ClassesBySignature" },
- { 1, 3, "VirtualMachine.AllClasses" },
- { 1, 4, "VirtualMachine.AllThreads" },
- { 1, 5, "VirtualMachine.TopLevelThreadGroups" },
- { 1, 6, "VirtualMachine.Dispose" },
- { 1, 7, "VirtualMachine.IDSizes" },
- { 1, 8, "VirtualMachine.Suspend" },
- { 1, 9, "VirtualMachine.Resume" },
- { 1, 10, "VirtualMachine.Exit" },
- { 1, 11, "VirtualMachine.CreateString" },
- { 1, 12, "VirtualMachine.Capabilities" },
- { 1, 13, "VirtualMachine.ClassPaths" },
- { 1, 14, "VirtualMachine.DisposeObjects" },
- { 1, 15, "VirtualMachine.HoldEvents" },
- { 1, 16, "VirtualMachine.ReleaseEvents" },
- { 1, 17, "VirtualMachine.CapabilitiesNew" },
- { 1, 18, "VirtualMachine.RedefineClasses" },
- { 1, 19, "VirtualMachine.SetDefaultStratum" },
- { 1, 20, "VirtualMachine.AllClassesWithGeneric"},
- { 1, 21, "VirtualMachine.InstanceCounts"},
-
- /* ReferenceType command set (2) */
- { 2, 1, "ReferenceType.Signature" },
- { 2, 2, "ReferenceType.ClassLoader" },
- { 2, 3, "ReferenceType.Modifiers" },
- { 2, 4, "ReferenceType.Fields" },
- { 2, 5, "ReferenceType.Methods" },
- { 2, 6, "ReferenceType.GetValues" },
- { 2, 7, "ReferenceType.SourceFile" },
- { 2, 8, "ReferenceType.NestedTypes" },
- { 2, 9, "ReferenceType.Status" },
- { 2, 10, "ReferenceType.Interfaces" },
- { 2, 11, "ReferenceType.ClassObject" },
- { 2, 12, "ReferenceType.SourceDebugExtension" },
- { 2, 13, "ReferenceType.SignatureWithGeneric" },
- { 2, 14, "ReferenceType.FieldsWithGeneric" },
- { 2, 15, "ReferenceType.MethodsWithGeneric" },
- { 2, 16, "ReferenceType.Instances" },
- { 2, 17, "ReferenceType.ClassFileVersion" },
- { 2, 18, "ReferenceType.ConstantPool" },
-
- /* ClassType command set (3) */
- { 3, 1, "ClassType.Superclass" },
- { 3, 2, "ClassType.SetValues" },
- { 3, 3, "ClassType.InvokeMethod" },
- { 3, 4, "ClassType.NewInstance" },
-
- /* ArrayType command set (4) */
- { 4, 1, "ArrayType.NewInstance" },
-
- /* InterfaceType command set (5) */
-
- /* Method command set (6) */
- { 6, 1, "Method.LineTable" },
- { 6, 2, "Method.VariableTable" },
- { 6, 3, "Method.Bytecodes" },
- { 6, 4, "Method.IsObsolete" },
- { 6, 5, "Method.VariableTableWithGeneric" },
-
- /* Field command set (8) */
-
- /* ObjectReference command set (9) */
- { 9, 1, "ObjectReference.ReferenceType" },
- { 9, 2, "ObjectReference.GetValues" },
- { 9, 3, "ObjectReference.SetValues" },
- { 9, 4, "ObjectReference.UNUSED" },
- { 9, 5, "ObjectReference.MonitorInfo" },
- { 9, 6, "ObjectReference.InvokeMethod" },
- { 9, 7, "ObjectReference.DisableCollection" },
- { 9, 8, "ObjectReference.EnableCollection" },
- { 9, 9, "ObjectReference.IsCollected" },
- { 9, 10, "ObjectReference.ReferringObjects" },
-
- /* StringReference command set (10) */
- { 10, 1, "StringReference.Value" },
-
- /* ThreadReference command set (11) */
- { 11, 1, "ThreadReference.Name" },
- { 11, 2, "ThreadReference.Suspend" },
- { 11, 3, "ThreadReference.Resume" },
- { 11, 4, "ThreadReference.Status" },
- { 11, 5, "ThreadReference.ThreadGroup" },
- { 11, 6, "ThreadReference.Frames" },
- { 11, 7, "ThreadReference.FrameCount" },
- { 11, 8, "ThreadReference.OwnedMonitors" },
- { 11, 9, "ThreadReference.CurrentContendedMonitor" },
- { 11, 10, "ThreadReference.Stop" },
- { 11, 11, "ThreadReference.Interrupt" },
- { 11, 12, "ThreadReference.SuspendCount" },
- { 11, 13, "ThreadReference.OwnedMonitorsStackDepthInfo" },
- { 11, 14, "ThreadReference.ForceEarlyReturn" },
-
- /* ThreadGroupReference command set (12) */
- { 12, 1, "ThreadGroupReference.Name" },
- { 12, 2, "ThreadGroupReference.Parent" },
- { 12, 3, "ThreadGroupReference.Children" },
-
- /* ArrayReference command set (13) */
- { 13, 1, "ArrayReference.Length" },
- { 13, 2, "ArrayReference.GetValues" },
- { 13, 3, "ArrayReference.SetValues" },
-
- /* ClassLoaderReference command set (14) */
- { 14, 1, "ArrayReference.VisibleClasses" },
-
- /* EventRequest command set (15) */
- { 15, 1, "EventRequest.Set" },
- { 15, 2, "EventRequest.Clear" },
- { 15, 3, "EventRequest.ClearAllBreakpoints" },
-
- /* StackFrame command set (16) */
- { 16, 1, "StackFrame.GetValues" },
- { 16, 2, "StackFrame.SetValues" },
- { 16, 3, "StackFrame.ThisObject" },
- { 16, 4, "StackFrame.PopFrames" },
-
- /* ClassObjectReference command set (17) */
- { 17, 1, "ClassObjectReference.ReflectedType" },
-
- /* Event command set (64) */
- { 64, 100, "Event.Composite" },
-
- /* DDMS */
- { 199, 1, "DDMS.Chunk" },
-};
-
-/*
- * Look up a command's name.
- */
-static const char* getCommandName(int cmdSet, int cmd)
-{
- for (int i = 0; i < (int) NELEM(gHandlerMap); i++) {
- if (gHandlerMap[i].cmdSet == cmdSet &&
- gHandlerMap[i].cmd == cmd)
- {
- return gHandlerMap[i].descr;
- }
- }
-
- return "?UNKNOWN?";
-}
-
-
-void jdwpNetFree(NetState* netState); /* fwd */
-
-/*
- * Allocate state structure and bind to the listen port.
- *
- * Returns 0 on success.
- */
-NetState* jdwpNetStartup(unsigned short listenPort, const char* connectHost,
- unsigned short connectPort)
-{
- NetState* netState = (NetState*) malloc(sizeof(*netState));
- memset(netState, 0, sizeof(*netState));
- netState->listenSock = -1;
- netState->dbg.sock = netState->vm.sock = -1;
-
- strcpy(netState->dbg.label, "D");
- strcpy(netState->vm.label, "V");
-
- /*
- * Set up a socket to listen for connections from the debugger.
- */
-
- netState->listenSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (netState->listenSock < 0) {
- fprintf(stderr, "Socket create failed: %s\n", strerror(errno));
- goto fail;
- }
-
- /* allow immediate re-use if we die */
- {
- int one = 1;
- if (setsockopt(netState->listenSock, SOL_SOCKET, SO_REUSEADDR, &one,
- sizeof(one)) < 0)
- {
- fprintf(stderr, "setsockopt(SO_REUSEADDR) failed: %s\n",
- strerror(errno));
- goto fail;
- }
- }
-
- struct sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_port = htons(listenPort);
- addr.sin_addr.s_addr = INADDR_ANY;
-
- if (bind(netState->listenSock, (struct sockaddr*) &addr, sizeof(addr)) != 0)
- {
- fprintf(stderr, "attempt to bind to port %u failed: %s\n",
- listenPort, strerror(errno));
- goto fail;
- }
-
- fprintf(stderr, "+++ bound to port %u\n", listenPort);
-
- if (listen(netState->listenSock, 5) != 0) {
- fprintf(stderr, "Listen failed: %s\n", strerror(errno));
- goto fail;
- }
-
- /*
- * Do the hostname lookup for the VM.
- */
- struct hostent* pHost;
-
- pHost = gethostbyname(connectHost);
- if (pHost == NULL) {
- fprintf(stderr, "Name lookup of '%s' failed: %s\n",
- connectHost, strerror(h_errno));
- goto fail;
- }
-
- netState->vmAddr = *((struct in_addr*) pHost->h_addr_list[0]);
- netState->vmPort = connectPort;
-
- fprintf(stderr, "+++ connect host resolved to %s\n",
- inet_ntoa(netState->vmAddr));
-
- return netState;
-
-fail:
- jdwpNetFree(netState);
- return NULL;
-}
-
-/*
- * Shut down JDWP listener. Don't free state.
- *
- * Note that "netState" may be partially initialized if "startup" failed.
- */
-void jdwpNetShutdown(NetState* netState)
-{
- int listenSock = netState->listenSock;
- int dbgSock = netState->dbg.sock;
- int vmSock = netState->vm.sock;
-
- /* clear these out so it doesn't wake up and try to reuse them */
- /* (important when multi-threaded) */
- netState->listenSock = netState->dbg.sock = netState->vm.sock = -1;
-
- if (listenSock >= 0) {
- shutdown(listenSock, SHUT_RDWR);
- close(listenSock);
- }
- if (dbgSock >= 0) {
- shutdown(dbgSock, SHUT_RDWR);
- close(dbgSock);
- }
- if (vmSock >= 0) {
- shutdown(vmSock, SHUT_RDWR);
- close(vmSock);
- }
-}
-
-/*
- * Shut down JDWP listener and free its state.
- */
-void jdwpNetFree(NetState* netState)
-{
- if (netState == NULL)
- return;
-
- jdwpNetShutdown(netState);
- free(netState);
-}
-
-/*
- * Disable the TCP Nagle algorithm, which delays transmission of outbound
- * packets until the previous transmissions have been acked. JDWP does a
- * lot of back-and-forth with small packets, so this may help.
- */
-static int setNoDelay(int fd)
-{
- int cc, on = 1;
-
- cc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
- assert(cc == 0);
- return cc;
-}
-
-/*
- * Accept a connection. This will block waiting for somebody to show up.
- */
-bool jdwpAcceptConnection(NetState* netState)
-{
- struct sockaddr_in addr;
- socklen_t addrlen;
- int sock;
-
- if (netState->listenSock < 0)
- return false; /* you're not listening! */
-
- assert(netState->dbg.sock < 0); /* must not already be talking */
-
- addrlen = sizeof(addr);
- do {
- sock = accept(netState->listenSock, (struct sockaddr*) &addr, &addrlen);
- if (sock < 0 && errno != EINTR) {
- fprintf(stderr, "accept failed: %s\n", strerror(errno));
- return false;
- }
- } while (sock < 0);
-
- fprintf(stderr, "+++ accepted connection from %s:%u\n",
- inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
-
- netState->dbg.sock = sock;
- netState->dbg.awaitingHandshake = true;
- netState->dbg.inputCount = 0;
-
- setNoDelay(sock);
-
- return true;
-}
-
-/*
- * Close the connections to the debugger and VM.
- *
- * Reset the state so we're ready to receive a new connection.
- */
-void jdwpCloseConnection(NetState* netState)
-{
- if (netState->dbg.sock >= 0) {
- fprintf(stderr, "+++ closing connection to debugger\n");
- close(netState->dbg.sock);
- netState->dbg.sock = -1;
- }
- if (netState->vm.sock >= 0) {
- fprintf(stderr, "+++ closing connection to vm\n");
- close(netState->vm.sock);
- netState->vm.sock = -1;
- }
-}
-
-/*
- * Figure out if we have a full packet in the buffer.
- */
-static bool haveFullPacket(Peer* pPeer)
-{
- long length;
-
- if (pPeer->awaitingHandshake)
- return (pPeer->inputCount >= kMagicHandshakeLen);
-
- if (pPeer->inputCount < 4)
- return false;
-
- length = get4BE(pPeer->inputBuffer);
- return (pPeer->inputCount >= length);
-}
-
-/*
- * Consume bytes from the buffer.
- *
- * This would be more efficient with a circular buffer. However, we're
- * usually only going to find one packet, which is trivial to handle.
- */
-static void consumeBytes(Peer* pPeer, int count)
-{
- assert(count > 0);
- assert(count <= pPeer->inputCount);
-
- if (count == pPeer->inputCount) {
- pPeer->inputCount = 0;
- return;
- }
-
- memmove(pPeer->inputBuffer, pPeer->inputBuffer + count,
- pPeer->inputCount - count);
- pPeer->inputCount -= count;
-}
-
-/*
- * Get the current time.
- */
-static void getCurrentTime(int* pMin, int* pSec)
-{
- time_t now;
- struct tm* ptm;
-
- now = time(NULL);
- ptm = localtime(&now);
- *pMin = ptm->tm_min;
- *pSec = ptm->tm_sec;
-}
-
-/*
- * Dump the contents of a packet to stdout.
- */
-static void dumpPacket(const unsigned char* packetBuf, const char* srcName,
- const char* dstName)
-{
- const unsigned char* buf = packetBuf;
- char prefix[3];
- u4 length, id;
- u1 flags, cmdSet=0, cmd=0;
- JdwpError error = ERR_NONE;
- bool reply;
- int dataLen;
-
- length = get4BE(buf+0);
- id = get4BE(buf+4);
- flags = get1(buf+8);
- if ((flags & kJDWPFlagReply) != 0) {
- reply = true;
- error = static_cast<JdwpError>(get2BE(buf+9));
- } else {
- reply = false;
- cmdSet = get1(buf+9);
- cmd = get1(buf+10);
- }
-
- buf += kJDWPHeaderLen;
- dataLen = length - (buf - packetBuf);
-
- if (!reply) {
- prefix[0] = srcName[0];
- prefix[1] = '>';
- } else {
- prefix[0] = dstName[0];
- prefix[1] = '<';
- }
- prefix[2] = '\0';
-
- int min, sec;
- getCurrentTime(&min, &sec);
-
- if (!reply) {
- printf("%s REQUEST dataLen=%-5u id=0x%08x flags=0x%02x cmd=%d/%d [%02d:%02d]\n",
- prefix, dataLen, id, flags, cmdSet, cmd, min, sec);
- printf("%s --> %s\n", prefix, getCommandName(cmdSet, cmd));
- } else {
- printf("%s REPLY dataLen=%-5u id=0x%08x flags=0x%02x err=%d (%s) [%02d:%02d]\n",
- prefix, dataLen, id, flags, error, dvmJdwpErrorStr(error), min,sec);
- }
- if (dataLen > 0)
- printHexDump2(buf, dataLen, prefix);
- printf("%s ----------\n", prefix);
-}
-
-/*
- * Handle a packet. Returns "false" if we encounter a connection-fatal error.
- */
-static bool handlePacket(Peer* pDst, Peer* pSrc)
-{
- const unsigned char* buf = pSrc->inputBuffer;
- u4 length;
- u1 flags;
- int cc;
-
- length = get4BE(buf+0);
- flags = get1(buf+9);
-
- assert((int) length <= pSrc->inputCount);
-
- dumpPacket(buf, pSrc->label, pDst->label);
-
- cc = write(pDst->sock, buf, length);
- if (cc != (int) length) {
- fprintf(stderr, "Failed sending packet: %s\n", strerror(errno));
- return false;
- }
- /*printf("*** wrote %d bytes from %c to %c\n",
- cc, pSrc->label[0], pDst->label[0]);*/
-
- consumeBytes(pSrc, length);
- return true;
-}
-
-/*
- * Handle incoming data. If we have a full packet in the buffer, process it.
- */
-static bool handleIncoming(Peer* pWritePeer, Peer* pReadPeer)
-{
- if (haveFullPacket(pReadPeer)) {
- if (pReadPeer->awaitingHandshake) {
- printf("Handshake [%c]: %.14s\n",
- pReadPeer->label[0], pReadPeer->inputBuffer);
- if (write(pWritePeer->sock, pReadPeer->inputBuffer,
- kMagicHandshakeLen) != kMagicHandshakeLen)
- {
- fprintf(stderr,
- "+++ [%c] handshake write failed\n", pReadPeer->label[0]);
- goto fail;
- }
- consumeBytes(pReadPeer, kMagicHandshakeLen);
- pReadPeer->awaitingHandshake = false;
- } else {
- if (!handlePacket(pWritePeer, pReadPeer))
- goto fail;
- }
- } else {
- /*printf("*** %c not full yet\n", pReadPeer->label[0]);*/
- }
-
- return true;
-
-fail:
- return false;
-}
-
-/*
- * Process incoming data. If no data is available, this will block until
- * some arrives.
- *
- * Returns "false" on error (indicating that the connection has been severed).
- */
-bool jdwpProcessIncoming(NetState* netState)
-{
- int cc;
-
- assert(netState->dbg.sock >= 0);
- assert(netState->vm.sock >= 0);
-
- while (!haveFullPacket(&netState->dbg) && !haveFullPacket(&netState->vm)) {
- /* read some more */
- int highFd;
- fd_set readfds;
-
- highFd = (netState->dbg.sock > netState->vm.sock) ?
- netState->dbg.sock+1 : netState->vm.sock+1;
- FD_ZERO(&readfds);
- FD_SET(netState->dbg.sock, &readfds);
- FD_SET(netState->vm.sock, &readfds);
-
- errno = 0;
- cc = select(highFd, &readfds, NULL, NULL, NULL);
- if (cc < 0) {
- if (errno == EINTR) {
- fprintf(stderr, "+++ EINTR on select\n");
- continue;
- }
- fprintf(stderr, "+++ select failed: %s\n", strerror(errno));
- goto fail;
- }
-
- if (FD_ISSET(netState->dbg.sock, &readfds)) {
- cc = read(netState->dbg.sock,
- netState->dbg.inputBuffer + netState->dbg.inputCount,
- sizeof(netState->dbg.inputBuffer) - netState->dbg.inputCount);
- if (cc < 0) {
- if (errno == EINTR) {
- fprintf(stderr, "+++ EINTR on read\n");
- continue;
- }
- fprintf(stderr, "+++ dbg read failed: %s\n", strerror(errno));
- goto fail;
- }
- if (cc == 0) {
- if (sizeof(netState->dbg.inputBuffer) ==
- netState->dbg.inputCount)
- fprintf(stderr, "+++ debugger sent huge message\n");
- else
- fprintf(stderr, "+++ debugger disconnected\n");
- goto fail;
- }
-
- /*printf("*** %d bytes from dbg\n", cc);*/
- netState->dbg.inputCount += cc;
- }
-
- if (FD_ISSET(netState->vm.sock, &readfds)) {
- cc = read(netState->vm.sock,
- netState->vm.inputBuffer + netState->vm.inputCount,
- sizeof(netState->vm.inputBuffer) - netState->vm.inputCount);
- if (cc < 0) {
- if (errno == EINTR) {
- fprintf(stderr, "+++ EINTR on read\n");
- continue;
- }
- fprintf(stderr, "+++ vm read failed: %s\n", strerror(errno));
- goto fail;
- }
- if (cc == 0) {
- if (sizeof(netState->vm.inputBuffer) ==
- netState->vm.inputCount)
- fprintf(stderr, "+++ vm sent huge message\n");
- else
- fprintf(stderr, "+++ vm disconnected\n");
- goto fail;
- }
-
- /*printf("*** %d bytes from vm\n", cc);*/
- netState->vm.inputCount += cc;
- }
- }
-
- if (!handleIncoming(&netState->dbg, &netState->vm))
- goto fail;
- if (!handleIncoming(&netState->vm, &netState->dbg))
- goto fail;
-
- return true;
-
-fail:
- jdwpCloseConnection(netState);
- return false;
-}
-
-/*
- * Connect to the VM.
- */
-bool jdwpConnectToVm(NetState* netState)
-{
- struct sockaddr_in addr;
- int sock = -1;
-
- sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (sock < 0) {
- fprintf(stderr, "Socket create failed: %s\n", strerror(errno));
- goto fail;
- }
-
- addr.sin_family = AF_INET;
- addr.sin_addr = netState->vmAddr;
- addr.sin_port = htons(netState->vmPort);
- if (connect(sock, (struct sockaddr*) &addr, sizeof(addr)) != 0) {
- fprintf(stderr, "Connection to %s:%u failed: %s\n",
- inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), strerror(errno));
- goto fail;
- }
- fprintf(stderr, "+++ connected to VM %s:%u\n",
- inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
-
- netState->vm.sock = sock;
- netState->vm.awaitingHandshake = true;
- netState->vm.inputCount = 0;
-
- setNoDelay(netState->vm.sock);
- return true;
-
-fail:
- if (sock >= 0)
- close(sock);
- return false;
-}
-
-/*
- * Establish network connections and start things running.
- *
- * We wait for a new connection from the debugger. When one arrives we
- * open a connection to the VM. If one side or the other goes away, we
- * drop both ends and go back to listening.
- */
-int run(const char* connectHost, int connectPort, int listenPort)
-{
- NetState* state;
-
- state = jdwpNetStartup(listenPort, connectHost, connectPort);
- if (state == NULL)
- return -1;
-
- while (true) {
- if (!jdwpAcceptConnection(state))
- break;
-
- if (jdwpConnectToVm(state)) {
- while (true) {
- if (!jdwpProcessIncoming(state))
- break;
- }
- }
-
- jdwpCloseConnection(state);
- }
-
- jdwpNetFree(state);
-
- return 0;
-}
diff --git a/tools/jdwpspy/find_JdwpConstants.cpp b/tools/jdwpspy/find_JdwpConstants.cpp
deleted file mode 100644
index 57b7dbb..0000000
--- a/tools/jdwpspy/find_JdwpConstants.cpp
+++ /dev/null
@@ -1 +0,0 @@
-#include "jdwp/JdwpConstants.cpp"
diff --git a/vm/Globals.h b/vm/Globals.h
index d4076e9..29f7356 100644
--- a/vm/Globals.h
+++ b/vm/Globals.h
@@ -90,6 +90,7 @@
size_t heapStartingSize;
size_t heapMaximumSize;
size_t heapGrowthLimit;
+ bool lowMemoryMode;
double heapTargetUtilization;
size_t heapMinFree;
size_t heapMaxFree;
@@ -267,6 +268,7 @@
ClassObject* classJavaLangReflectMethod;
ClassObject* classJavaLangReflectMethodArray;
ClassObject* classJavaLangReflectProxy;
+ ClassObject* classJavaLangSystem;
ClassObject* classJavaNioDirectByteBuffer;
ClassObject* classLibcoreReflectAnnotationFactory;
ClassObject* classLibcoreReflectAnnotationMember;
@@ -406,6 +408,9 @@
/* field offsets - java.lang.reflect.Proxy */
int offJavaLangReflectProxy_h;
+ /* direct method pointer - java.lang.System.runFinalization */
+ Method* methJavaLangSystem_runFinalization;
+
/* field offsets - java.io.FileDescriptor */
int offJavaIoFileDescriptor_descriptor;
@@ -736,6 +741,8 @@
#if defined(WITH_JIT)
+#define DEFAULT_CODE_CACHE_SIZE 0xffffffff
+
/* Trace profiling modes. Ordering matters - off states before on states */
enum TraceProfilingModes {
kTraceProfilingDisabled = 0, // Not profiling
@@ -800,7 +807,7 @@
/* How many entries in the JitEntryTable are in use */
unsigned int jitTableEntriesUsed;
- /* Bytes allocated for the code cache */
+ /* Max bytes allocated for the code cache. Rough rule of thumb: 1K per 1M of system RAM */
unsigned int codeCacheSize;
/* Trigger for trace selection */
diff --git a/vm/Init.cpp b/vm/Init.cpp
index 4e547d3..136e1cb 100644
--- a/vm/Init.cpp
+++ b/vm/Init.cpp
@@ -140,6 +140,7 @@
"[,hexopvalue[-endvalue]]*\n");
dvmFprintf(stderr, " -Xincludeselectedmethod\n");
dvmFprintf(stderr, " -Xjitthreshold:decimalvalue\n");
+ dvmFprintf(stderr, " -Xjitcodecachesize:decimalvalueofkbytes\n");
dvmFprintf(stderr, " -Xjitblocking\n");
dvmFprintf(stderr, " -Xjitmethod:signature[,signature]* "
"(eg Ljava/lang/String\\;replace)\n");
@@ -941,6 +942,8 @@
dvmFprintf(stderr, "Invalid -XX:HeapMaxFree option '%s'\n", argv[i]);
return -1;
}
+ } else if (strcmp(argv[i], "-XX:LowMemoryMode") == 0) {
+ gDvm.lowMemoryMode = true;
} else if (strncmp(argv[i], "-XX:HeapTargetUtilization=", 26) == 0) {
const char* start = argv[i] + 26;
const char* end = start;
@@ -1120,6 +1123,11 @@
gDvmJit.blockingMode = true;
} else if (strncmp(argv[i], "-Xjitthreshold:", 15) == 0) {
gDvmJit.threshold = atoi(argv[i] + 15);
+ } else if (strncmp(argv[i], "-Xjitcodecachesize:", 19) == 0) {
+ gDvmJit.codeCacheSize = atoi(argv[i] + 19) * 1024;
+ if (gDvmJit.codeCacheSize == 0) {
+ gDvm.executionMode = kExecutionModeInterpFast;
+ }
} else if (strncmp(argv[i], "-Xincludeselectedop", 19) == 0) {
gDvmJit.includeSelectedOp = true;
} else if (strncmp(argv[i], "-Xincludeselectedmethod", 23) == 0) {
@@ -1235,6 +1243,7 @@
gDvm.heapStartingSize = 2 * 1024 * 1024; // Spec says 16MB; too big for us.
gDvm.heapMaximumSize = 16 * 1024 * 1024; // Spec says 75% physical mem
gDvm.heapGrowthLimit = 0; // 0 means no growth limit
+ gDvm.lowMemoryMode = false;
gDvm.stackSize = kDefaultStackSize;
gDvm.mainThreadStackSize = kDefaultStackSize;
// When the heap is less than the maximum or growth limited size,
@@ -1275,6 +1284,7 @@
gDvmJit.includeSelectedOffset = false;
gDvmJit.methodTable = NULL;
gDvmJit.classTable = NULL;
+ gDvmJit.codeCacheSize = DEFAULT_CODE_CACHE_SIZE;
gDvm.constInit = false;
gDvm.commonInit = false;
@@ -1700,7 +1710,7 @@
const char* target_base = getenv("EMULATED_STORAGE_TARGET");
if (target_base != NULL) {
if (mount("tmpfs", target_base, "tmpfs", MS_NOSUID | MS_NODEV,
- "uid=0,gid=1028,mode=0050") == -1) {
+ "uid=0,gid=1028,mode=0751") == -1) {
SLOGE("Failed to mount tmpfs to %s: %s", target_base, strerror(errno));
return -1;
}
@@ -1728,13 +1738,10 @@
#ifdef HAVE_ANDROID_OS
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
- if (errno == EINVAL) {
- SLOGW("PR_SET_NO_NEW_PRIVS failed. "
- "Is your kernel compiled correctly?: %s", strerror(errno));
- // Don't return -1 here, since it's expected that not all
- // kernels will support this option.
- } else {
- SLOGW("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno));
+ // Older kernels don't understand PR_SET_NO_NEW_PRIVS and return
+ // EINVAL. Don't die on such kernels.
+ if (errno != EINVAL) {
+ SLOGE("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno));
return -1;
}
}
diff --git a/vm/InitRefs.cpp b/vm/InitRefs.cpp
index 9a04842..08c28f8 100644
--- a/vm/InitRefs.cpp
+++ b/vm/InitRefs.cpp
@@ -127,6 +127,7 @@
{ &gDvm.classJavaLangReflectMethod, "Ljava/lang/reflect/Method;" },
{ &gDvm.classJavaLangReflectMethodArray, "[Ljava/lang/reflect/Method;"},
{ &gDvm.classJavaLangReflectProxy, "Ljava/lang/reflect/Proxy;" },
+ { &gDvm.classJavaLangSystem, "Ljava/lang/System;" },
{ &gDvm.classJavaNioDirectByteBuffer, "Ljava/nio/DirectByteBuffer;" },
{ &gDvm.classOrgApacheHarmonyDalvikDdmcChunk, "Lorg/apache/harmony/dalvik/ddmc/Chunk;" },
{ &gDvm.classOrgApacheHarmonyDalvikDdmcDdmServer,
@@ -357,6 +358,9 @@
"getSystemClassLoader", "()Ljava/lang/ClassLoader;" },
{ &gDvm.methJavaLangReflectProxy_constructorPrototype, "Ljava/lang/reflect/Proxy;",
"constructorPrototype", "(Ljava/lang/reflect/InvocationHandler;)V" },
+ { &gDvm.methJavaLangSystem_runFinalization, "Ljava/lang/System;",
+ "runFinalization", "()V" },
+
{ &gDvm.methodTraceGcMethod, "Ldalvik/system/VMDebug;", "startGC", "()V" },
{ &gDvm.methodTraceClassPrepMethod, "Ldalvik/system/VMDebug;", "startClassPrep", "()V" },
{ &gDvm.methOrgApacheHarmonyLangAnnotationAnnotationFactory_createAnnotation,
diff --git a/vm/alloc/Alloc.cpp b/vm/alloc/Alloc.cpp
index cfcb968..3d40d11 100644
--- a/vm/alloc/Alloc.cpp
+++ b/vm/alloc/Alloc.cpp
@@ -17,9 +17,12 @@
* Garbage-collecting memory allocator.
*/
#include "Dalvik.h"
+#include "Globals.h"
#include "alloc/Heap.h"
#include "alloc/HeapInternal.h"
#include "alloc/HeapSource.h"
+#include "cutils/atomic.h"
+#include "cutils/atomic-inline.h"
/*
* Initialize the GC universe.
@@ -305,6 +308,17 @@
dvmUnlockHeap();
}
+/*
+ * Run finalization.
+ */
+void dvmRunFinalization() {
+ Thread *self = dvmThreadSelf();
+ assert(self != NULL);
+ JValue unusedResult;
+ assert(gDvm.methJavaLangSystem_runFinalization != NULL);
+ dvmCallMethod(self, gDvm.methJavaLangSystem_runFinalization, NULL, &unusedResult);
+}
+
struct CountContext {
const ClassObject *clazz;
size_t count;
diff --git a/vm/alloc/Alloc.h b/vm/alloc/Alloc.h
index efee1bd..b838719 100644
--- a/vm/alloc/Alloc.h
+++ b/vm/alloc/Alloc.h
@@ -125,6 +125,11 @@
void dvmCollectGarbage(void);
/*
+ * Calls System.runFinalization().
+ */
+void dvmRunFinalization();
+
+/*
* Returns a count of the direct instances of a class.
*/
size_t dvmCountInstancesOfClass(const ClassObject *clazz);
diff --git a/vm/alloc/CardTable.cpp b/vm/alloc/CardTable.cpp
index 28fe58e..87143dc 100644
--- a/vm/alloc/CardTable.cpp
+++ b/vm/alloc/CardTable.cpp
@@ -133,21 +133,20 @@
*/
assert(gDvm.gcHeap->cardTableBase != NULL);
-#if 1
- // zero out cards with memset(), using liveBits as an estimate
- const HeapBitmap* liveBits = dvmHeapSourceGetLiveBits();
- size_t maxLiveCard = (liveBits->max - liveBits->base) / GC_CARD_SIZE;
- maxLiveCard = ALIGN_UP_TO_PAGE_SIZE(maxLiveCard);
- if (maxLiveCard > gDvm.gcHeap->cardTableLength) {
- maxLiveCard = gDvm.gcHeap->cardTableLength;
- }
+ if (gDvm.lowMemoryMode) {
+ // zero out cards with madvise(), discarding all pages in the card table
+ madvise(gDvm.gcHeap->cardTableBase, gDvm.gcHeap->cardTableLength, MADV_DONTNEED);
+ } else {
+ // zero out cards with memset(), using liveBits as an estimate
+ const HeapBitmap* liveBits = dvmHeapSourceGetLiveBits();
+ size_t maxLiveCard = (liveBits->max - liveBits->base) / GC_CARD_SIZE;
+ maxLiveCard = ALIGN_UP_TO_PAGE_SIZE(maxLiveCard);
+ if (maxLiveCard > gDvm.gcHeap->cardTableLength) {
+ maxLiveCard = gDvm.gcHeap->cardTableLength;
+ }
- memset(gDvm.gcHeap->cardTableBase, GC_CARD_CLEAN, maxLiveCard);
-#else
- // zero out cards with madvise(), discarding all pages in the card table
- madvise(gDvm.gcHeap->cardTableBase, gDvm.gcHeap->cardTableLength,
- MADV_DONTNEED);
-#endif
+ memset(gDvm.gcHeap->cardTableBase, GC_CARD_CLEAN, maxLiveCard);
+ }
}
/*
diff --git a/vm/alloc/Heap.cpp b/vm/alloc/Heap.cpp
index 4a8d165..2de20ef 100644
--- a/vm/alloc/Heap.cpp
+++ b/vm/alloc/Heap.cpp
@@ -29,8 +29,9 @@
#include "alloc/MarkSweep.h"
#include "os/os.h"
-#include <sys/time.h>
+#include <sys/mman.h>
#include <sys/resource.h>
+#include <sys/time.h>
#include <limits.h>
#include <errno.h>
diff --git a/vm/alloc/HeapSource.cpp b/vm/alloc/HeapSource.cpp
index 35e11c7..22de1b5 100644
--- a/vm/alloc/HeapSource.cpp
+++ b/vm/alloc/HeapSource.cpp
@@ -17,6 +17,7 @@
#include <stdint.h>
#include <sys/mman.h>
#include <errno.h>
+#include <cutils/ashmem.h>
#define SIZE_MAX UINT_MAX // TODO: get SIZE_MAX from stdint.h
@@ -28,6 +29,7 @@
#include "alloc/HeapBitmap.h"
#include "alloc/HeapBitmapInlines.h"
+static void dvmHeapSourceUpdateMaxNativeFootprint();
static void snapIdealFootprint();
static void setIdealFootprint(size_t max);
static size_t getMaximumSize(const HeapSource *hs);
@@ -177,6 +179,14 @@
HeapBitmap markBits;
/*
+ * Native allocations.
+ */
+ int32_t nativeBytesAllocated;
+ size_t nativeFootprintGCWatermark;
+ size_t nativeFootprintLimit;
+ bool nativeNeedToRunFinalization;
+
+ /*
* State for the GC daemon.
*/
bool hasGcThread;
@@ -377,6 +387,36 @@
}
/*
+ * A helper for addNewHeap(). Remap the new heap so that it will have
+ * a separate ashmem region with possibly a different name, etc. In
+ * practice, this is used to give the app heap a separate ashmem
+ * region from the zygote heap's.
+ */
+static bool remapNewHeap(HeapSource* hs, Heap* newHeap)
+{
+ char* newHeapBase = newHeap->base;
+ size_t rem_size = hs->heapBase + hs->heapLength - newHeapBase;
+ munmap(newHeapBase, rem_size);
+ int fd = ashmem_create_region("dalvik-heap", rem_size);
+ if (fd == -1) {
+ ALOGE("Unable to create an ashmem region for the new heap");
+ return false;
+ }
+ void* addr = mmap(newHeapBase, rem_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ int ret = close(fd);
+ if (addr == MAP_FAILED) {
+ ALOGE("Unable to map an ashmem region for the new heap");
+ return false;
+ }
+ if (ret == -1) {
+ ALOGE("Unable to close fd for the ashmem region for the new heap");
+ munmap(newHeapBase, rem_size);
+ return false;
+ }
+ return true;
+}
+
+/*
* Adds an additional heap to the heap source. Returns false if there
* are too many heaps or insufficient free space to add another heap.
*/
@@ -414,6 +454,9 @@
heap.base = base;
heap.limit = heap.base + heap.maximumSize;
heap.brk = heap.base + morecoreStart;
+ if (!remapNewHeap(hs, &heap)) {
+ return false;
+ }
heap.msp = createMspace(base, morecoreStart, hs->minFree);
if (heap.msp == NULL) {
return false;
@@ -568,7 +611,7 @@
* among the heaps managed by the garbage collector.
*/
length = ALIGN_UP_TO_PAGE_SIZE(maximumSize);
- base = dvmAllocRegion(length, PROT_NONE, "dalvik-heap");
+ base = dvmAllocRegion(length, PROT_NONE, gDvm.zygote ? "dalvik-zygote" : "dalvik-heap");
if (base == NULL) {
dvmAbort();
}
@@ -603,6 +646,10 @@
hs->softLimit = SIZE_MAX; // no soft limit at first
hs->numHeaps = 0;
hs->sawZygote = gDvm.zygote;
+ hs->nativeBytesAllocated = 0;
+ hs->nativeFootprintGCWatermark = startSize;
+ hs->nativeFootprintLimit = startSize * 2;
+ hs->nativeNeedToRunFinalization = false;
hs->hasGcThread = false;
hs->heapBase = (char *)base;
hs->heapLength = length;
@@ -878,10 +925,45 @@
FRACTIONAL_MB(hs->softLimit), n);
return NULL;
}
- void* ptr = mspace_calloc(heap->msp, 1, n);
- if (ptr == NULL) {
- return NULL;
+ void* ptr;
+ if (gDvm.lowMemoryMode) {
+ /* This is only necessary because mspace_calloc always memsets the
+ * allocated memory to 0. This is bad for memory usage since it leads
+ * to dirty zero pages. If low memory mode is enabled, we use
+ * mspace_malloc which doesn't memset the allocated memory and madvise
+ * the page aligned region back to the kernel.
+ */
+ ptr = mspace_malloc(heap->msp, n);
+ if (ptr == NULL) {
+ return NULL;
+ }
+ uintptr_t zero_begin = (uintptr_t)ptr;
+ uintptr_t zero_end = (uintptr_t)ptr + n;
+ /* Calculate the page aligned region.
+ */
+ uintptr_t begin = ALIGN_UP_TO_PAGE_SIZE(zero_begin);
+ uintptr_t end = zero_end & ~(uintptr_t)(SYSTEM_PAGE_SIZE - 1);
+ /* If our allocation spans more than one page, we attempt to madvise.
+ */
+ if (begin < end) {
+ /* madvise the page aligned region to kernel.
+ */
+ madvise((void*)begin, end - begin, MADV_DONTNEED);
+ /* Zero the region after the page aligned region.
+ */
+ memset((void*)end, 0, zero_end - end);
+ /* Zero out the region before the page aligned region.
+ */
+ zero_end = begin;
+ }
+ memset((void*)zero_begin, 0, zero_end - zero_begin);
+ } else {
+ ptr = mspace_calloc(heap->msp, 1, n);
+ if (ptr == NULL) {
+ return NULL;
+ }
}
+
countAllocation(heap, ptr);
/*
* Check to see if a concurrent GC should be initiated.
@@ -1324,6 +1406,11 @@
} else {
heap->concurrentStartBytes = freeBytes - CONCURRENT_START;
}
+
+ /* Mark that we need to run finalizers and update the native watermarks
+ * next time we attempt to register a native allocation.
+ */
+ gHs->nativeNeedToRunFinalization = true;
}
/*
@@ -1418,3 +1505,95 @@
return NULL;
}
}
+
+static void dvmHeapSourceUpdateMaxNativeFootprint()
+{
+ /* Use the current target utilization ratio to determine the new native GC
+ * watermarks.
+ */
+ size_t nativeSize = gHs->nativeBytesAllocated;
+ size_t targetSize =
+ (nativeSize / gHs->targetUtilization) * HEAP_UTILIZATION_MAX;
+
+ if (targetSize > nativeSize + gHs->maxFree) {
+ targetSize = nativeSize + gHs->maxFree;
+ } else if (targetSize < nativeSize + gHs->minFree) {
+ targetSize = nativeSize + gHs->minFree;
+ }
+ gHs->nativeFootprintGCWatermark = targetSize;
+ gHs->nativeFootprintLimit = 2 * targetSize - nativeSize;
+}
+
+void dvmHeapSourceRegisterNativeAllocation(int bytes)
+{
+ /* If we have just done a GC, ensure that the finalizers are done and update
+ * the native watermarks.
+ */
+ if (gHs->nativeNeedToRunFinalization) {
+ dvmRunFinalization();
+ dvmHeapSourceUpdateMaxNativeFootprint();
+ gHs->nativeNeedToRunFinalization = false;
+ }
+
+ android_atomic_add(bytes, &gHs->nativeBytesAllocated);
+
+ if ((size_t)gHs->nativeBytesAllocated > gHs->nativeFootprintGCWatermark) {
+ /* The second watermark is higher than the gc watermark. If you hit
+ * this it means you are allocating native objects faster than the GC
+ * can keep up with. If this occurs, we do a GC for alloc.
+ */
+ if ((size_t)gHs->nativeBytesAllocated > gHs->nativeFootprintLimit) {
+ Thread* self = dvmThreadSelf();
+ dvmRunFinalization();
+ if (dvmCheckException(self)) {
+ return;
+ }
+ dvmLockHeap();
+ bool waited = dvmWaitForConcurrentGcToComplete();
+ dvmUnlockHeap();
+ if (waited) {
+ // Just finished a GC, attempt to run finalizers.
+ dvmRunFinalization();
+ if (dvmCheckException(self)) {
+ return;
+ }
+ }
+
+ // If we still are over the watermark, attempt a GC for alloc and run finalizers.
+ if ((size_t)gHs->nativeBytesAllocated > gHs->nativeFootprintLimit) {
+ dvmLockHeap();
+ dvmWaitForConcurrentGcToComplete();
+ dvmCollectGarbageInternal(GC_FOR_MALLOC);
+ dvmUnlockHeap();
+ dvmRunFinalization();
+ gHs->nativeNeedToRunFinalization = false;
+ if (dvmCheckException(self)) {
+ return;
+ }
+ }
+ /* We have just run finalizers, update the native watermark since
+ * it is very likely that finalizers released native managed
+ * allocations.
+ */
+ dvmHeapSourceUpdateMaxNativeFootprint();
+ } else {
+ dvmSignalCond(&gHs->gcThreadCond);
+ }
+ }
+}
+
+/*
+ * Called from VMRuntime.registerNativeFree.
+ */
+void dvmHeapSourceRegisterNativeFree(int bytes)
+{
+ int expected_size, new_size;
+ do {
+ expected_size = gHs->nativeBytesAllocated;
+ new_size = expected_size - bytes;
+ if (new_size < 0) {
+ break;
+ }
+ } while (android_atomic_cas(expected_size, new_size,
+ &gHs->nativeBytesAllocated));
+}
diff --git a/vm/alloc/HeapSource.h b/vm/alloc/HeapSource.h
index e1f6820..42fccb2 100644
--- a/vm/alloc/HeapSource.h
+++ b/vm/alloc/HeapSource.h
@@ -200,4 +200,14 @@
*/
size_t dvmHeapSourceGetMaximumSize(void);
+/*
+ * Called from VMRuntime.registerNativeAllocation.
+ */
+void dvmHeapSourceRegisterNativeAllocation(int bytes);
+
+/*
+ * Called from VMRuntime.registerNativeFree.
+ */
+void dvmHeapSourceRegisterNativeFree(int bytes);
+
#endif // DALVIK_HEAP_SOURCE_H_
diff --git a/vm/compiler/Compiler.cpp b/vm/compiler/Compiler.cpp
index b1ba96f..f5b96b1 100644
--- a/vm/compiler/Compiler.cpp
+++ b/vm/compiler/Compiler.cpp
@@ -182,7 +182,7 @@
MAP_PRIVATE , fd, 0);
close(fd);
if (gDvmJit.codeCache == MAP_FAILED) {
- ALOGE("Failed to mmap the JIT code cache: %s", strerror(errno));
+ ALOGE("Failed to mmap the JIT code cache of size %d: %s", gDvmJit.codeCacheSize, strerror(errno));
return false;
}
diff --git a/vm/compiler/codegen/arm/Assemble.cpp b/vm/compiler/codegen/arm/Assemble.cpp
index a729dc5..10572eb 100644
--- a/vm/compiler/codegen/arm/Assemble.cpp
+++ b/vm/compiler/codegen/arm/Assemble.cpp
@@ -2153,6 +2153,8 @@
}
ALOGD("JIT: Average execution count -> %d",(int)(sum / numTraces));
+ // How efficiently are we using code cache memory? Bigger is better.
+ ALOGD("JIT: CodeCache efficiency -> %.2f",(float)sum / (float)gDvmJit.codeCacheByteUsed);
/* Dump the sorted entries. The count of each trace will be reset to 0. */
for (i=0; i < gDvmJit.jitTableSize; i++) {
diff --git a/vm/compiler/codegen/arm/armv5te-vfp/ArchVariant.cpp b/vm/compiler/codegen/arm/armv5te-vfp/ArchVariant.cpp
index 713ecfa..6c89b11 100644
--- a/vm/compiler/codegen/arm/armv5te-vfp/ArchVariant.cpp
+++ b/vm/compiler/codegen/arm/armv5te-vfp/ArchVariant.cpp
@@ -55,7 +55,13 @@
if (gDvmJit.threshold == 0) {
gDvmJit.threshold = 200;
}
- gDvmJit.codeCacheSize = 512*1024;
+ if (gDvmJit.codeCacheSize == DEFAULT_CODE_CACHE_SIZE) {
+ gDvmJit.codeCacheSize = 512 * 1024;
+ } else if ((gDvmJit.codeCacheSize == 0) && (gDvm.executionMode == kExecutionModeJit)) {
+ gDvm.executionMode = kExecutionModeInterpFast;
+ }
+ /* Hard limit for Arm of 2M */
+ assert(gDvmJit.codeCacheSize <= 2 * 1024 * 1024);
#if defined(WITH_SELF_VERIFICATION)
/* Force into blocking mode */
diff --git a/vm/compiler/codegen/arm/armv5te/ArchVariant.cpp b/vm/compiler/codegen/arm/armv5te/ArchVariant.cpp
index 25d650e..5c7fbbe 100644
--- a/vm/compiler/codegen/arm/armv5te/ArchVariant.cpp
+++ b/vm/compiler/codegen/arm/armv5te/ArchVariant.cpp
@@ -55,7 +55,13 @@
if (gDvmJit.threshold == 0) {
gDvmJit.threshold = 200;
}
- gDvmJit.codeCacheSize = 512*1024;
+ if (gDvmJit.codeCacheSize == DEFAULT_CODE_CACHE_SIZE) {
+ gDvmJit.codeCacheSize = 512 * 1024;
+ } else if ((gDvmJit.codeCacheSize == 0) && (gDvm.executionMode == kExecutionModeJit)) {
+ gDvm.executionMode = kExecutionModeInterpFast;
+ }
+ /* Hard limit for Arm of 2M */
+ assert(gDvmJit.codeCacheSize <= 2 * 1024 * 1024);
#if defined(WITH_SELF_VERIFICATION)
/* Force into blocking mode */
diff --git a/vm/compiler/codegen/arm/armv7-a-neon/ArchVariant.cpp b/vm/compiler/codegen/arm/armv7-a-neon/ArchVariant.cpp
index 0b1e0cd..a81a2e7 100644
--- a/vm/compiler/codegen/arm/armv7-a-neon/ArchVariant.cpp
+++ b/vm/compiler/codegen/arm/armv7-a-neon/ArchVariant.cpp
@@ -50,7 +50,13 @@
if (gDvmJit.threshold == 0) {
gDvmJit.threshold = 40;
}
- gDvmJit.codeCacheSize = 1024*1024;
+ if (gDvmJit.codeCacheSize == DEFAULT_CODE_CACHE_SIZE) {
+ gDvmJit.codeCacheSize = 1500 * 1024;
+ } else if ((gDvmJit.codeCacheSize == 0) && (gDvm.executionMode == kExecutionModeJit)) {
+ gDvm.executionMode = kExecutionModeInterpFast;
+ }
+ /* Hard limit for Arm of 2M */
+ assert(gDvmJit.codeCacheSize <= 2 * 1024 * 1024);
#if defined(WITH_SELF_VERIFICATION)
/* Force into blocking */
diff --git a/vm/compiler/codegen/arm/armv7-a/ArchVariant.cpp b/vm/compiler/codegen/arm/armv7-a/ArchVariant.cpp
index c698f62..72ae3ce 100644
--- a/vm/compiler/codegen/arm/armv7-a/ArchVariant.cpp
+++ b/vm/compiler/codegen/arm/armv7-a/ArchVariant.cpp
@@ -50,7 +50,13 @@
if (gDvmJit.threshold == 0) {
gDvmJit.threshold = 40;
}
- gDvmJit.codeCacheSize = 1024*1024;
+ if (gDvmJit.codeCacheSize == DEFAULT_CODE_CACHE_SIZE) {
+ gDvmJit.codeCacheSize = 1500 * 1024;
+ } else if ((gDvmJit.codeCacheSize == 0) && (gDvm.executionMode == kExecutionModeJit)) {
+ gDvm.executionMode = kExecutionModeInterpFast;
+ }
+ /* Hard limit for Arm of 2M */
+ assert(gDvmJit.codeCacheSize <= 2 * 1024 * 1024);
#if defined(WITH_SELF_VERIFICATION)
/* Force into blocking */
diff --git a/vm/compiler/codegen/mips/mips/ArchVariant.cpp b/vm/compiler/codegen/mips/mips/ArchVariant.cpp
index 4362961..473b88e 100644
--- a/vm/compiler/codegen/mips/mips/ArchVariant.cpp
+++ b/vm/compiler/codegen/mips/mips/ArchVariant.cpp
@@ -55,7 +55,11 @@
if (gDvmJit.threshold == 0) {
gDvmJit.threshold = 200;
}
- gDvmJit.codeCacheSize = 512*1024;
+ if (gDvmJit.codeCacheSize == DEFAULT_CODE_CACHE_SIZE) {
+ gDvmJit.codeCacheSize = 512 * 1024;
+ } else if ((gDvmJit.codeCacheSize == 0) && (gDvm.executionMode == kExecutionModeJit)) {
+ gDvm.executionMode = kExecutionModeInterpFast;
+ }
#if defined(WITH_SELF_VERIFICATION)
/* Force into blocking mode */
diff --git a/vm/compiler/codegen/x86/CodegenInterface.cpp b/vm/compiler/codegen/x86/CodegenInterface.cpp
index 3027929..337bd61 100644
--- a/vm/compiler/codegen/x86/CodegenInterface.cpp
+++ b/vm/compiler/codegen/x86/CodegenInterface.cpp
@@ -67,7 +67,11 @@
if (gDvmJit.threshold == 0) {
gDvmJit.threshold = 255;
}
- gDvmJit.codeCacheSize = 512*1024;
+ if (gDvmJit.codeCacheSize == DEFAULT_CODE_CACHE_SIZE) {
+ gDvmJit.codeCacheSize = 512 * 1024;
+ } else if ((gDvmJit.codeCacheSize == 0) && (gDvm.executionMode == kExecutionModeJit)) {
+ gDvm.executionMode = kExecutionModeInterpFast;
+ }
gDvmJit.optLevel = kJitOptLevelO1;
//Disable Method-JIT
diff --git a/vm/interp/InterpState.h b/vm/interp/InterpState.h
index 6ef4472..8d2c224 100644
--- a/vm/interp/InterpState.h
+++ b/vm/interp/InterpState.h
@@ -192,7 +192,7 @@
/* Number of entries in the 2nd level JIT profiler filter cache */
#define JIT_TRACE_THRESH_FILTER_SIZE 32
/* Number of low dalvik pc address bits to include in 2nd level filter key */
-#define JIT_TRACE_THRESH_FILTER_PC_BITS 4
+#define JIT_TRACE_THRESH_FILTER_PC_BITS 16
#define MAX_JIT_RUN_LEN 64
enum JitHint {
diff --git a/vm/native/dalvik_system_VMDebug.cpp b/vm/native/dalvik_system_VMDebug.cpp
index dae8997..5377357 100644
--- a/vm/native/dalvik_system_VMDebug.cpp
+++ b/vm/native/dalvik_system_VMDebug.cpp
@@ -18,6 +18,7 @@
* dalvik.system.VMDebug
*/
#include "Dalvik.h"
+#include "alloc/HeapSource.h"
#include "native/InternalNativePriv.h"
#include "hprof/Hprof.h"
@@ -770,11 +771,50 @@
}
}
+/*
+ * public static native void getHeapSpaceStats(long[] data)
+ */
+static void Dalvik_dalvik_system_VMDebug_getHeapSpaceStats(const u4* args,
+ JValue* pResult)
+{
+ ArrayObject* dataArray = (ArrayObject*) args[0];
+
+ if (dataArray == NULL || dataArray->length < 6) {
+ RETURN_VOID();
+ }
+
+ jlong* arr = (jlong*)(void*)dataArray->contents;
+
+ int j = 0;
+ size_t per_heap_allocated[2];
+ size_t per_heap_size[2];
+ memset(per_heap_allocated, 0, sizeof(per_heap_allocated));
+ memset(per_heap_size, 0, sizeof(per_heap_size));
+ dvmHeapSourceGetValue(HS_BYTES_ALLOCATED, (size_t*) &per_heap_allocated, 2);
+ dvmHeapSourceGetValue(HS_FOOTPRINT, (size_t*) &per_heap_size, 2);
+ jlong heapSize = per_heap_size[0];
+ jlong heapUsed = per_heap_allocated[0];
+ jlong heapFree = heapSize - heapUsed;
+ jlong zygoteSize = per_heap_size[1];
+ jlong zygoteUsed = per_heap_allocated[1];
+ jlong zygoteFree = zygoteSize - zygoteUsed;
+ arr[j++] = heapSize;
+ arr[j++] = heapUsed;
+ arr[j++] = heapFree;
+ arr[j++] = zygoteSize;
+ arr[j++] = zygoteUsed;
+ arr[j++] = zygoteFree;
+
+ RETURN_VOID();
+}
+
const DalvikNativeMethod dvm_dalvik_system_VMDebug[] = {
{ "getVmFeatureList", "()[Ljava/lang/String;",
Dalvik_dalvik_system_VMDebug_getVmFeatureList },
{ "getAllocCount", "(I)I",
Dalvik_dalvik_system_VMDebug_getAllocCount },
+ { "getHeapSpaceStats", "([J)V",
+ Dalvik_dalvik_system_VMDebug_getHeapSpaceStats },
{ "resetAllocCount", "(I)V",
Dalvik_dalvik_system_VMDebug_resetAllocCount },
{ "startAllocCounting", "()V",
diff --git a/vm/native/dalvik_system_VMRuntime.cpp b/vm/native/dalvik_system_VMRuntime.cpp
index 8211342..1f7a883 100644
--- a/vm/native/dalvik_system_VMRuntime.cpp
+++ b/vm/native/dalvik_system_VMRuntime.cpp
@@ -19,10 +19,15 @@
*/
#include "Dalvik.h"
#include "ScopedPthreadMutexLock.h"
+#include "UniquePtr.h"
+#include "alloc/HeapSource.h"
+#include "alloc/Visit.h"
+#include "libdex/DexClass.h"
#include "native/InternalNativePriv.h"
#include <limits.h>
+#include <map>
/*
* public native float getTargetHeapUtilization()
@@ -184,6 +189,12 @@
returnCString(pResult, buf);
}
+static void Dalvik_dalvik_system_VMRuntime_vmLibrary(const u4* args,
+ JValue* pResult)
+{
+ returnCString(pResult, "libdvm.so");
+}
+
static void Dalvik_dalvik_system_VMRuntime_setTargetSdkVersionNative(
const u4* args,
JValue* pResult)
@@ -204,6 +215,339 @@
RETURN_VOID();
}
+static void Dalvik_dalvik_system_VMRuntime_registerNativeAllocation(const u4* args,
+ JValue* pResult)
+{
+ int bytes = args[1];
+ if (bytes < 0) {
+ dvmThrowRuntimeException("allocation size negative");
+ } else {
+ dvmHeapSourceRegisterNativeAllocation(bytes);
+ }
+ RETURN_VOID();
+}
+
+static void Dalvik_dalvik_system_VMRuntime_registerNativeFree(const u4* args,
+ JValue* pResult)
+{
+ int bytes = args[1];
+ if (bytes < 0) {
+ dvmThrowRuntimeException("allocation size negative");
+ } else {
+ dvmHeapSourceRegisterNativeFree(bytes);
+ }
+ RETURN_VOID();
+}
+
+static DvmDex* getDvmDexFromClassPathEntry(ClassPathEntry* cpe) {
+ if (cpe->kind == kCpeDex) {
+ return ((RawDexFile*) cpe->ptr)->pDvmDex;
+ }
+ if (cpe->kind == kCpeJar) {
+ return ((JarFile*) cpe->ptr)->pDvmDex;
+ }
+ LOG_ALWAYS_FATAL("Unknown cpe->kind=%d", cpe->kind);
+}
+
+typedef std::map<std::string, StringObject*> StringTable;
+
+static void preloadDexCachesStringsVisitor(void* addr, u4 threadId, RootType type, void* arg) {
+ StringTable& table = *(StringTable*) arg;
+ StringObject* strObj = *(StringObject**) addr;
+ LOG_FATAL_IF(strObj->clazz != gDvm.classJavaLangString, "Unknown class for supposed string");
+ char* newStr = dvmCreateCstrFromString(strObj);
+ // ALOGI("VMRuntime.preloadDexCaches interned=%s", newStr);
+ table[newStr] = strObj;
+ free(newStr);
+}
+
+// Based on dvmResolveString.
+static void preloadDexCachesResolveString(DvmDex* pDvmDex,
+ uint32_t stringIdx,
+ StringTable& strings) {
+ StringObject* string = dvmDexGetResolvedString(pDvmDex, stringIdx);
+ if (string != NULL) {
+ return;
+ }
+ const DexFile* pDexFile = pDvmDex->pDexFile;
+ uint32_t utf16Size;
+ const char* utf8 = dexStringAndSizeById(pDexFile, stringIdx, &utf16Size);
+ string = strings[utf8];
+ if (string == NULL) {
+ return;
+ }
+ // ALOGI("VMRuntime.preloadDexCaches found string=%s", utf8);
+ dvmDexSetResolvedString(pDvmDex, stringIdx, string);
+}
+
+// Based on dvmResolveClass.
+static void preloadDexCachesResolveType(DvmDex* pDvmDex, uint32_t typeIdx) {
+ ClassObject* clazz = dvmDexGetResolvedClass(pDvmDex, typeIdx);
+ if (clazz != NULL) {
+ return;
+ }
+ const DexFile* pDexFile = pDvmDex->pDexFile;
+ const char* className = dexStringByTypeIdx(pDexFile, typeIdx);
+ if (className[0] != '\0' && className[1] == '\0') {
+ /* primitive type */
+ clazz = dvmFindPrimitiveClass(className[0]);
+ } else {
+ clazz = dvmLookupClass(className, NULL, true);
+ }
+ if (clazz == NULL) {
+ return;
+ }
+ // Skip uninitialized classes because filled cache entry implies it is initialized.
+ if (!dvmIsClassInitialized(clazz)) {
+ // ALOGI("VMRuntime.preloadDexCaches uninitialized clazz=%s", className);
+ return;
+ }
+ // ALOGI("VMRuntime.preloadDexCaches found clazz=%s", className);
+ dvmDexSetResolvedClass(pDvmDex, typeIdx, clazz);
+}
+
+// Based on dvmResolveInstField/dvmResolveStaticField.
+static void preloadDexCachesResolveField(DvmDex* pDvmDex, uint32_t fieldIdx, bool instance) {
+ Field* field = dvmDexGetResolvedField(pDvmDex, fieldIdx);
+ if (field != NULL) {
+ return;
+ }
+ const DexFile* pDexFile = pDvmDex->pDexFile;
+ const DexFieldId* pFieldId = dexGetFieldId(pDexFile, fieldIdx);
+ ClassObject* clazz = dvmDexGetResolvedClass(pDvmDex, pFieldId->classIdx);
+ if (clazz == NULL) {
+ return;
+ }
+ // Skip static fields for uninitialized classes because a filled
+ // cache entry implies the class is initialized.
+ if (!instance && !dvmIsClassInitialized(clazz)) {
+ return;
+ }
+ const char* fieldName = dexStringById(pDexFile, pFieldId->nameIdx);
+ const char* signature = dexStringByTypeIdx(pDexFile, pFieldId->typeIdx);
+ if (instance) {
+ field = dvmFindInstanceFieldHier(clazz, fieldName, signature);
+ } else {
+ field = dvmFindStaticFieldHier(clazz, fieldName, signature);
+ }
+ if (field == NULL) {
+ return;
+ }
+ // ALOGI("VMRuntime.preloadDexCaches found field %s %s.%s",
+ // signature, clazz->descriptor, fieldName);
+ dvmDexSetResolvedField(pDvmDex, fieldIdx, field);
+}
+
+// Based on dvmResolveMethod.
+static void preloadDexCachesResolveMethod(DvmDex* pDvmDex,
+ uint32_t methodIdx,
+ MethodType methodType) {
+ Method* method = dvmDexGetResolvedMethod(pDvmDex, methodIdx);
+ if (method != NULL) {
+ return;
+ }
+ const DexFile* pDexFile = pDvmDex->pDexFile;
+ const DexMethodId* pMethodId = dexGetMethodId(pDexFile, methodIdx);
+ ClassObject* clazz = dvmDexGetResolvedClass(pDvmDex, pMethodId->classIdx);
+ if (clazz == NULL) {
+ return;
+ }
+ // Skip static methods for uninitialized classes because a filled
+ // cache entry implies the class is initialized.
+ if ((methodType == METHOD_STATIC) && !dvmIsClassInitialized(clazz)) {
+ return;
+ }
+ const char* methodName = dexStringById(pDexFile, pMethodId->nameIdx);
+ DexProto proto;
+ dexProtoSetFromMethodId(&proto, pDexFile, pMethodId);
+
+ if (methodType == METHOD_DIRECT) {
+ method = dvmFindDirectMethod(clazz, methodName, &proto);
+ } else if (methodType == METHOD_STATIC) {
+ method = dvmFindDirectMethodHier(clazz, methodName, &proto);
+ } else {
+ method = dvmFindVirtualMethodHier(clazz, methodName, &proto);
+ }
+ if (method == NULL) {
+ return;
+ }
+ // ALOGI("VMRuntime.preloadDexCaches found method %s.%s",
+ // clazz->descriptor, methodName);
+ dvmDexSetResolvedMethod(pDvmDex, methodIdx, method);
+}
+
+struct DexCacheStats {
+ uint32_t numStrings;
+ uint32_t numTypes;
+ uint32_t numFields;
+ uint32_t numMethods;
+ DexCacheStats() : numStrings(0), numTypes(0), numFields(0), numMethods(0) {};
+};
+
+static const bool kPreloadDexCachesEnabled = true;
+
+// Disabled because it takes a long time (extra half second) but
+// gives almost no benefit in terms of saving private dirty pages.
+static const bool kPreloadDexCachesStrings = false;
+
+static const bool kPreloadDexCachesTypes = true;
+static const bool kPreloadDexCachesFieldsAndMethods = true;
+
+static const bool kPreloadDexCachesCollectStats = false;
+
+static void preloadDexCachesStatsTotal(DexCacheStats* total) {
+ if (!kPreloadDexCachesCollectStats) {
+ return;
+ }
+
+ for (ClassPathEntry* cpe = gDvm.bootClassPath; cpe->kind != kCpeLastEntry; cpe++) {
+ DvmDex* pDvmDex = getDvmDexFromClassPathEntry(cpe);
+ const DexHeader* pHeader = pDvmDex->pHeader;
+ total->numStrings += pHeader->stringIdsSize;
+ total->numFields += pHeader->fieldIdsSize;
+ total->numMethods += pHeader->methodIdsSize;
+ total->numTypes += pHeader->typeIdsSize;
+ }
+}
+
+static void preloadDexCachesStatsFilled(DexCacheStats* filled) {
+ if (!kPreloadDexCachesCollectStats) {
+ return;
+ }
+ for (ClassPathEntry* cpe = gDvm.bootClassPath; cpe->kind != kCpeLastEntry; cpe++) {
+ DvmDex* pDvmDex = getDvmDexFromClassPathEntry(cpe);
+ const DexHeader* pHeader = pDvmDex->pHeader;
+ for (size_t i = 0; i < pHeader->stringIdsSize; i++) {
+ StringObject* string = dvmDexGetResolvedString(pDvmDex, i);
+ if (string != NULL) {
+ filled->numStrings++;
+ }
+ }
+ for (size_t i = 0; i < pHeader->typeIdsSize; i++) {
+ ClassObject* clazz = dvmDexGetResolvedClass(pDvmDex, i);
+ if (clazz != NULL) {
+ filled->numTypes++;
+ }
+ }
+ for (size_t i = 0; i < pHeader->fieldIdsSize; i++) {
+ Field* field = dvmDexGetResolvedField(pDvmDex, i);
+ if (field != NULL) {
+ filled->numFields++;
+ }
+ }
+ for (size_t i = 0; i < pHeader->methodIdsSize; i++) {
+ Method* method = dvmDexGetResolvedMethod(pDvmDex, i);
+ if (method != NULL) {
+ filled->numMethods++;
+ }
+ }
+ }
+}
+
+static void Dalvik_dalvik_system_VMRuntime_preloadDexCaches(const u4* args, JValue* pResult)
+{
+ if (!kPreloadDexCachesEnabled) {
+ return;
+ }
+
+ DexCacheStats total;
+ DexCacheStats before;
+ if (kPreloadDexCachesCollectStats) {
+ ALOGI("VMRuntime.preloadDexCaches starting");
+ preloadDexCachesStatsTotal(&total);
+ preloadDexCachesStatsFilled(&before);
+ }
+
+ // We use a std::map to avoid heap allocating StringObjects to lookup in gDvm.literalStrings
+ StringTable strings;
+ if (kPreloadDexCachesStrings) {
+ dvmLockMutex(&gDvm.internLock);
+ dvmHashTableLock(gDvm.literalStrings);
+ for (int i = 0; i < gDvm.literalStrings->tableSize; ++i) {
+ HashEntry *entry = &gDvm.literalStrings->pEntries[i];
+ if (entry->data != NULL && entry->data != HASH_TOMBSTONE) {
+ preloadDexCachesStringsVisitor(&entry->data, 0, ROOT_INTERNED_STRING, &strings);
+ }
+ }
+ dvmHashTableUnlock(gDvm.literalStrings);
+ dvmUnlockMutex(&gDvm.internLock);
+ }
+
+ for (ClassPathEntry* cpe = gDvm.bootClassPath; cpe->kind != kCpeLastEntry; cpe++) {
+ DvmDex* pDvmDex = getDvmDexFromClassPathEntry(cpe);
+ const DexHeader* pHeader = pDvmDex->pHeader;
+ const DexFile* pDexFile = pDvmDex->pDexFile;
+
+ if (kPreloadDexCachesStrings) {
+ for (size_t i = 0; i < pHeader->stringIdsSize; i++) {
+ preloadDexCachesResolveString(pDvmDex, i, strings);
+ }
+ }
+
+ if (kPreloadDexCachesTypes) {
+ for (size_t i = 0; i < pHeader->typeIdsSize; i++) {
+ preloadDexCachesResolveType(pDvmDex, i);
+ }
+ }
+
+ if (kPreloadDexCachesFieldsAndMethods) {
+ for (size_t classDefIndex = 0;
+ classDefIndex < pHeader->classDefsSize;
+ classDefIndex++) {
+ const DexClassDef* pClassDef = dexGetClassDef(pDexFile, classDefIndex);
+ const u1* pEncodedData = dexGetClassData(pDexFile, pClassDef);
+ UniquePtr<DexClassData> pClassData(dexReadAndVerifyClassData(&pEncodedData, NULL));
+ if (pClassData.get() == NULL) {
+ continue;
+ }
+ for (uint32_t fieldIndex = 0;
+ fieldIndex < pClassData->header.staticFieldsSize;
+ fieldIndex++) {
+ const DexField* pField = &pClassData->staticFields[fieldIndex];
+ preloadDexCachesResolveField(pDvmDex, pField->fieldIdx, false);
+ }
+ for (uint32_t fieldIndex = 0;
+ fieldIndex < pClassData->header.instanceFieldsSize;
+ fieldIndex++) {
+ const DexField* pField = &pClassData->instanceFields[fieldIndex];
+ preloadDexCachesResolveField(pDvmDex, pField->fieldIdx, true);
+ }
+ for (uint32_t methodIndex = 0;
+ methodIndex < pClassData->header.directMethodsSize;
+ methodIndex++) {
+ const DexMethod* pDexMethod = &pClassData->directMethods[methodIndex];
+ MethodType methodType = (((pDexMethod->accessFlags & ACC_STATIC) != 0) ?
+ METHOD_STATIC :
+ METHOD_DIRECT);
+ preloadDexCachesResolveMethod(pDvmDex, pDexMethod->methodIdx, methodType);
+ }
+ for (uint32_t methodIndex = 0;
+ methodIndex < pClassData->header.virtualMethodsSize;
+ methodIndex++) {
+ const DexMethod* pDexMethod = &pClassData->virtualMethods[methodIndex];
+ preloadDexCachesResolveMethod(pDvmDex, pDexMethod->methodIdx, METHOD_VIRTUAL);
+ }
+ }
+ }
+ }
+
+ if (kPreloadDexCachesCollectStats) {
+ DexCacheStats after;
+ preloadDexCachesStatsFilled(&after);
+ ALOGI("VMRuntime.preloadDexCaches strings total=%d before=%d after=%d",
+ total.numStrings, before.numStrings, after.numStrings);
+ ALOGI("VMRuntime.preloadDexCaches types total=%d before=%d after=%d",
+ total.numTypes, before.numTypes, after.numTypes);
+ ALOGI("VMRuntime.preloadDexCaches fields total=%d before=%d after=%d",
+ total.numFields, before.numFields, after.numFields);
+ ALOGI("VMRuntime.preloadDexCaches methods total=%d before=%d after=%d",
+ total.numMethods, before.numMethods, after.numMethods);
+ ALOGI("VMRuntime.preloadDexCaches finished");
+ }
+
+ RETURN_VOID();
+}
+
const DalvikNativeMethod dvm_dalvik_system_VMRuntime[] = {
{ "addressOf", "(Ljava/lang/Object;)J",
Dalvik_dalvik_system_VMRuntime_addressOf },
@@ -231,5 +575,13 @@
Dalvik_dalvik_system_VMRuntime_startJitCompilation },
{ "vmVersion", "()Ljava/lang/String;",
Dalvik_dalvik_system_VMRuntime_vmVersion },
+ { "vmLibrary", "()Ljava/lang/String;",
+ Dalvik_dalvik_system_VMRuntime_vmLibrary },
+ { "registerNativeAllocation", "(I)V",
+ Dalvik_dalvik_system_VMRuntime_registerNativeAllocation },
+ { "registerNativeFree", "(I)V",
+ Dalvik_dalvik_system_VMRuntime_registerNativeFree },
+ { "preloadDexCaches", "()V",
+ Dalvik_dalvik_system_VMRuntime_preloadDexCaches },
{ NULL, NULL, NULL },
};
diff --git a/vm/native/dalvik_system_Zygote.cpp b/vm/native/dalvik_system_Zygote.cpp
index 9e3005a..86a8fb9 100644
--- a/vm/native/dalvik_system_Zygote.cpp
+++ b/vm/native/dalvik_system_Zygote.cpp
@@ -273,18 +273,14 @@
// Prepare source paths
char source_user[PATH_MAX];
- char source_obb[PATH_MAX];
char target_user[PATH_MAX];
// /mnt/shell/emulated/0
snprintf(source_user, PATH_MAX, "%s/%d", source, userid);
- // /mnt/shell/emulated/obb
- snprintf(source_obb, PATH_MAX, "%s/obb", source);
// /storage/emulated/0
snprintf(target_user, PATH_MAX, "%s/%d", target, userid);
if (fs_prepare_dir(source_user, 0000, 0, 0) == -1
- || fs_prepare_dir(source_obb, 0000, 0, 0) == -1
|| fs_prepare_dir(target_user, 0000, 0, 0) == -1) {
return -1;
}
@@ -303,23 +299,7 @@
}
}
- // Now that user is mounted, prepare and mount OBB storage
- // into place for current user
- char target_android[PATH_MAX];
- char target_obb[PATH_MAX];
-
- // /storage/emulated/0/Android
- snprintf(target_android, PATH_MAX, "%s/%d/Android", target, userid);
- // /storage/emulated/0/Android/obb
- snprintf(target_obb, PATH_MAX, "%s/%d/Android/obb", target, userid);
-
- if (fs_prepare_dir(target_android, 0000, 0, 0) == -1
- || fs_prepare_dir(target_obb, 0000, 0, 0) == -1
- || fs_prepare_dir(legacy, 0000, 0, 0) == -1) {
- return -1;
- }
- if (mount(source_obb, target_obb, NULL, MS_BIND, NULL) == -1) {
- ALOGE("Failed to mount %s to %s: %s", source_obb, target_obb, strerror(errno));
+ if (fs_prepare_dir(legacy, 0000, 0, 0) == -1) {
return -1;
}
diff --git a/vm/native/java_lang_Class.cpp b/vm/native/java_lang_Class.cpp
index 8687138..52c8c3e 100644
--- a/vm/native/java_lang_Class.cpp
+++ b/vm/native/java_lang_Class.cpp
@@ -771,14 +771,10 @@
if (dvm_dex == NULL) {
return NULL;
}
-
- ScopedPthreadMutexLock lock(&dvm_dex->modLock);
-
// Already cached?
if (dvm_dex->dex_object != NULL) {
return dvm_dex->dex_object;
}
-
jobject byte_buffer = env->NewDirectByteBuffer(dvm_dex->memMap.addr, dvm_dex->memMap.length);
if (byte_buffer == NULL) {
return NULL;
@@ -805,7 +801,12 @@
return NULL;
}
- dvm_dex->dex_object = env->NewGlobalRef(local_ref);
+ // Check another thread didn't cache an object, if we've won install the object.
+ ScopedPthreadMutexLock lock(&dvm_dex->modLock);
+
+ if (dvm_dex->dex_object == NULL) {
+ dvm_dex->dex_object = env->NewGlobalRef(local_ref);
+ }
return dvm_dex->dex_object;
}
diff --git a/vm/oo/Class.cpp b/vm/oo/Class.cpp
index 78a2273..db5340e 100644
--- a/vm/oo/Class.cpp
+++ b/vm/oo/Class.cpp
@@ -1740,6 +1740,9 @@
* Make sure the aren't any "bonus" flags set, since we use them for
* runtime state.
*/
+ /* bits we can reasonably expect to see set in a DEX access flags field */
+ const uint32_t EXPECTED_FILE_FLAGS = (ACC_CLASS_MASK | CLASS_ISPREVERIFIED |
+ CLASS_ISOPTIMIZED);
if ((pClassDef->accessFlags & ~EXPECTED_FILE_FLAGS) != 0) {
ALOGW("Invalid file flags in class %s: %04x",
descriptor, pClassDef->accessFlags);
diff --git a/vm/oo/Object.h b/vm/oo/Object.h
index ca41157..92438ba 100644
--- a/vm/oo/Object.h
+++ b/vm/oo/Object.h
@@ -82,10 +82,6 @@
CLASS_ISPREVERIFIED = (1<<16), // class has been pre-verified
};
-/* bits we can reasonably expect to see set in a DEX access flags field */
-#define EXPECTED_FILE_FLAGS \
- (ACC_CLASS_MASK | CLASS_ISPREVERIFIED | CLASS_ISOPTIMIZED)
-
/*
* Get/set class flags.
*/
diff --git a/vm/os/android.cpp b/vm/os/android.cpp
index 24ebd5a..b37bb70 100644
--- a/vm/os/android.cpp
+++ b/vm/os/android.cpp
@@ -23,8 +23,8 @@
#include <limits.h>
#include <errno.h>
+#include <system/thread_defs.h>
#include <cutils/sched_policy.h>
-#include <utils/threads.h>
/*
* Conversion map for "nice" values.