summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--runtime/Android.bp6
-rw-r--r--tools/titrace/Android.bp77
-rw-r--r--tools/titrace/README.md62
-rw-r--r--tools/titrace/instruction_decoder.cc518
-rw-r--r--tools/titrace/instruction_decoder.h42
-rw-r--r--tools/titrace/titrace.cc309
7 files changed, 1015 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp
index 0ce7916590..8678fd06d2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -41,4 +41,5 @@ subdirs = [
"test",
"tools/cpp-define-generator",
"tools/dmtracedump",
+ "tools/titrace",
]
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 6144869415..db9707f290 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -634,3 +634,9 @@ art_cc_test {
"libvixld-arm64",
],
}
+
+cc_library_headers {
+ name: "libart_runtime_headers",
+ host_supported: true,
+ export_include_dirs: ["."],
+}
diff --git a/tools/titrace/Android.bp b/tools/titrace/Android.bp
new file mode 100644
index 0000000000..b95ec9db25
--- /dev/null
+++ b/tools/titrace/Android.bp
@@ -0,0 +1,77 @@
+//
+// Copyright (C) 2017 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.
+//
+
+// Build variants {target,host} x {debug,ndebug} x {32,64}
+
+cc_defaults {
+ name: "titrace-defaults",
+ host_supported: true,
+ srcs: ["titrace.cc", "instruction_decoder.cc"],
+ defaults: ["art_defaults"],
+
+ // Note that this tool needs to be built for both 32-bit and 64-bit since it requires
+ // to be same ISA as what it is attached to.
+ compile_multilib: "both",
+
+ shared_libs: [
+ "libbase"
+ ],
+ target: {
+ android: {
+ },
+ host: {
+ },
+ },
+ header_libs: [
+ "libopenjdkjvmti_headers",
+ "libart_runtime_headers" // for dex_instruction_list.h only
+ // "libbase_headers",
+ ],
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+ symlink_preferred_arch: true,
+}
+
+art_cc_library {
+ name: "libtitrace",
+ defaults: ["titrace-defaults"],
+ shared_libs: [
+ ],
+}
+
+art_cc_library {
+ name: "libtitraced",
+ defaults: [
+ "art_debug_defaults",
+ "titrace-defaults",
+ ],
+ shared_libs: [
+ ],
+}
+
+//art_cc_test {
+// name: "art_titrace_tests",
+// defaults: [
+// "art_gtest_defaults",
+// ],
+// srcs: ["titrace_test.cc"],
+//}
diff --git a/tools/titrace/README.md b/tools/titrace/README.md
new file mode 100644
index 0000000000..a82025be8b
--- /dev/null
+++ b/tools/titrace/README.md
@@ -0,0 +1,62 @@
+# Titrace
+
+Titrace is a bytecode instruction tracing tool that uses JVMTI and works on both ART and the RI.
+
+# Usage
+### Build
+> `make libtitrace` # or 'make libtitraced' with debugging checks enabled
+
+The libraries will be built for 32-bit, 64-bit, host and target. Below examples assume you want to use the 64-bit version.
+### Command Line
+#### ART
+> `art -Xplugin:$ANDROID_HOST_OUT/lib64/libopenjdkjvmti.so -agentpath:$ANDROID_HOST_OUT/lib64/libtitrace.so -cp tmp/java/helloworld.dex -Xint helloworld`
+
+* `-Xplugin` and `-agentpath` need to be used, otherwise libtitrace agent will fail during init.
+* If using `libartd.so`, make sure to use the debug version of jvmti.
+#### Reference Implementation
+> `java -agentpath:$ANDROID_HOST_OUT/lib64/libtitrace.so helloworld`
+
+Only needs `-agentpath` to be specified.
+### Android Applications
+Replace __com.littleinc.orm_benchmark__ with the name of your application below.
+#### Enable permissions for attaching an agent
+Normal applications require that `debuggable=true` to be set in their AndroidManifest.xml.
+
+By using a *eng* or *userdebug* build of Android, we can override this requirement:
+> `adb root`
+> `adb shell setprop dalvik.vm.dex2oat-flags --debuggable`
+
+Then restart the runtime to pick it up.
+> `adb shell stop && adb shell start`
+
+If this step is skipped, attaching the agent will not succeed.
+#### Deploy agent to device
+The agent must be located in an app-accessible directory.
+
+> `adb push $ANDROID_PRODUCT_OUT/system/lib64/libtitrace.so /data/local/tmp`
+
+Upload to device first (it gets shell/root permissions).
+
+> `adb shell run-as com.littleinc.orm_benchmark 'cp /data/local/tmp/libtitrace.so /data/data/com.littleinc.orm_benchmark/files/libtitrace.so'`
+
+Copy the agent into an app-accessible directory, and make the file owned by the app.
+
+#### Attach agent to application
+
+##### Option 1: Attach the agent before any app code runs.
+> `adb shell am start --attach-agent /data/data/com.littleinc.orm_benchmark/files/libtitrace.so com.littleinc.orm_benchmark/.MainActivity`
+
+Note: To determine the arguments to `am start`, launch the application manually first and then look for this in logcat:
+
+> 09-14 13:28:08.680 7584 8192 I ActivityManager: Start proc 17614:com.littleinc.orm_benchmark/u0a138 for activity **com.littleinc.orm_benchmark/.MainActivity**
+
+##### Option 2: Attach the agent to an already-running app.
+> `adb shell am attach-agent $(pid com.littleinc.orm_benchmark) /data/data/com.littleinc.orm_benchmark/files/libtitrace.so`
+
+### Printing the Results
+All statitics gathered during the trace are printed automatically when the program normally exists. In the case of Android applications, they are always killed, so we need to manually print the results.
+
+> `kill -SIGQUIT $(pid com.littleinc.orm_benchmark)`
+
+Will initiate a dump of the agent (to logcat).
+
diff --git a/tools/titrace/instruction_decoder.cc b/tools/titrace/instruction_decoder.cc
new file mode 100644
index 0000000000..5d2f22feb3
--- /dev/null
+++ b/tools/titrace/instruction_decoder.cc
@@ -0,0 +1,518 @@
+// Copyright (C) 2017 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.
+//
+
+#include "instruction_decoder.h"
+
+#include "dex_instruction_list.h"
+
+#include <android-base/logging.h>
+
+namespace titrace {
+
+class ClassInstructionDecoder : public InstructionDecoder {
+ public:
+ virtual size_t GetMaximumOpcode() override {
+ return 0xff;
+ }
+
+ virtual const char* GetName(size_t opcode) override {
+ Bytecode::Opcode op = static_cast<Bytecode::Opcode>(opcode);
+ return Bytecode::ToString(op);
+ }
+
+ virtual size_t LocationToOffset(size_t j_location) {
+ return j_location;
+ }
+
+ private:
+ class Bytecode {
+ public:
+ enum Opcode {
+ // Java bytecode opcodes from 0x00 to 0xFF.
+ kNop = 0x00,
+ kAconst_null = 0x01,
+ kIconst_m1 = 0x02,
+ kIconst_0 = 0x03,
+ kIconst_1 = 0x04,
+ kIconst_2 = 0x05,
+ kIconst_3 = 0x06,
+ kIconst_4 = 0x07,
+ kIconst_5 = 0x08,
+ kLconst_0 = 0x09,
+ kLconst_1 = 0x0a,
+ kFconst_0 = 0x0b,
+ kFconst_1 = 0x0c,
+ kFconst_2 = 0x0d,
+ kDconst_0 = 0x0e,
+ kDconst_1 = 0x0f,
+ kBipush = 0x10,
+ kSipush = 0x11,
+ kLdc = 0x12,
+ kLdc_w = 0x13,
+ kLdc2_w = 0x14,
+ kIload = 0x15,
+ kLload = 0x16,
+ kFload = 0x17,
+ kDload = 0x18,
+ kAload = 0x19,
+ kIload_0 = 0x1a,
+ kIload_1 = 0x1b,
+ kIload_2 = 0x1c,
+ kIload_3 = 0x1d,
+ kLload_0 = 0x1e,
+ kLload_1 = 0x1f,
+ kLload_2 = 0x20,
+ kLload_3 = 0x21,
+ kFload_0 = 0x22,
+ kFload_1 = 0x23,
+ kFload_2 = 0x24,
+ kFload_3 = 0x25,
+ kDload_0 = 0x26,
+ kDload_1 = 0x27,
+ kDload_2 = 0x28,
+ kDload_3 = 0x29,
+ kAload_0 = 0x2a,
+ kAload_1 = 0x2b,
+ kAload_2 = 0x2c,
+ kAload_3 = 0x2d,
+ kIaload = 0x2e,
+ kLaload = 0x2f,
+ kFaload = 0x30,
+ kDaload = 0x31,
+ kAaload = 0x32,
+ kBaload = 0x33,
+ kCaload = 0x34,
+ kSaload = 0x35,
+ kIstore = 0x36,
+ kLstore = 0x37,
+ kFstore = 0x38,
+ kDstore = 0x39,
+ kAstore = 0x3a,
+ kIstore_0 = 0x3b,
+ kIstore_1 = 0x3c,
+ kIstore_2 = 0x3d,
+ kIstore_3 = 0x3e,
+ kLstore_0 = 0x3f,
+ kLstore_1 = 0x40,
+ kLstore_2 = 0x41,
+ kLstore_3 = 0x42,
+ kFstore_0 = 0x43,
+ kFstore_1 = 0x44,
+ kFstore_2 = 0x45,
+ kFstore_3 = 0x46,
+ kDstore_0 = 0x47,
+ kDstore_1 = 0x48,
+ kDstore_2 = 0x49,
+ kDstore_3 = 0x4a,
+ kAstore_0 = 0x4b,
+ kAstore_1 = 0x4c,
+ kAstore_2 = 0x4d,
+ kAstore_3 = 0x4e,
+ kIastore = 0x4f,
+ kLastore = 0x50,
+ kFastore = 0x51,
+ kDastore = 0x52,
+ kAastore = 0x53,
+ kBastore = 0x54,
+ kCastore = 0x55,
+ kSastore = 0x56,
+ kPop = 0x57,
+ kPop2 = 0x58,
+ kDup = 0x59,
+ kDup_x1 = 0x5a,
+ kDup_x2 = 0x5b,
+ kDup2 = 0x5c,
+ kDup2_x1 = 0x5d,
+ kDup2_x2 = 0x5e,
+ kSwap = 0x5f,
+ kIadd = 0x60,
+ kLadd = 0x61,
+ kFadd = 0x62,
+ kDadd = 0x63,
+ kIsub = 0x64,
+ kLsub = 0x65,
+ kFsub = 0x66,
+ kDsub = 0x67,
+ kImul = 0x68,
+ kLmul = 0x69,
+ kFmul = 0x6a,
+ kDmul = 0x6b,
+ kIdiv = 0x6c,
+ kLdiv = 0x6d,
+ kFdiv = 0x6e,
+ kDdiv = 0x6f,
+ kIrem = 0x70,
+ kLrem = 0x71,
+ kFrem = 0x72,
+ kDrem = 0x73,
+ kIneg = 0x74,
+ kLneg = 0x75,
+ kFneg = 0x76,
+ kDneg = 0x77,
+ kIshl = 0x78,
+ kLshl = 0x79,
+ kIshr = 0x7a,
+ kLshr = 0x7b,
+ kIushr = 0x7c,
+ kLushr = 0x7d,
+ kIand = 0x7e,
+ kLand = 0x7f,
+ kIor = 0x80,
+ kLor = 0x81,
+ kIxor = 0x82,
+ kLxor = 0x83,
+ kIinc = 0x84,
+ kI2l = 0x85,
+ kI2f = 0x86,
+ kI2d = 0x87,
+ kL2i = 0x88,
+ kL2f = 0x89,
+ kL2d = 0x8a,
+ kF2i = 0x8b,
+ kF2l = 0x8c,
+ kF2d = 0x8d,
+ kD2i = 0x8e,
+ kD2l = 0x8f,
+ kD2f = 0x90,
+ kI2b = 0x91,
+ kI2c = 0x92,
+ kI2s = 0x93,
+ kLcmp = 0x94,
+ kFcmpl = 0x95,
+ kFcmpg = 0x96,
+ kDcmpl = 0x97,
+ kDcmpg = 0x98,
+ kIfeq = 0x99,
+ kIfne = 0x9a,
+ kIflt = 0x9b,
+ kIfge = 0x9c,
+ kIfgt = 0x9d,
+ kIfle = 0x9e,
+ kIf_icmpeq = 0x9f,
+ kIf_icmpne = 0xa0,
+ kIf_icmplt = 0xa1,
+ kIf_icmpge = 0xa2,
+ kIf_icmpgt = 0xa3,
+ kIf_icmple = 0xa4,
+ kIf_acmpeq = 0xa5,
+ kIf_acmpne = 0xa6,
+ kGoto = 0xa7,
+ kJsr = 0xa8,
+ kRet = 0xa9,
+ kTableswitch = 0xaa,
+ kLookupswitch = 0xab,
+ kIreturn = 0xac,
+ kLreturn = 0xad,
+ kFreturn = 0xae,
+ kDreturn = 0xaf,
+ kAreturn = 0xb0,
+ kReturn = 0xb1,
+ kGetstatic = 0xb2,
+ kPutstatic = 0xb3,
+ kGetfield = 0xb4,
+ kPutfield = 0xb5,
+ kInvokevirtual = 0xb6,
+ kInvokespecial = 0xb7,
+ kInvokestatic = 0xb8,
+ kInvokeinterface = 0xb9,
+ kInvokedynamic = 0xba,
+ kNew = 0xbb,
+ kNewarray = 0xbc,
+ kAnewarray = 0xbd,
+ kArraylength = 0xbe,
+ kAthrow = 0xbf,
+ kCheckcast = 0xc0,
+ kInstanceof = 0xc1,
+ kMonitorenter = 0xc2,
+ kMonitorexit = 0xc3,
+ kWide = 0xc4,
+ kMultianewarray = 0xc5,
+ kIfnull = 0xc6,
+ kIfnonnull = 0xc7,
+ kGoto_w = 0xc8,
+ kJsr_w = 0xc9,
+ kBreakpoint = 0xca,
+ // Instructions 0xcb-0xfd are undefined.
+ kImpdep1 = 0xfe,
+ kImpdep2 = 0xff,
+ };
+
+ static const char* ToString(Bytecode::Opcode op) {
+ switch (op) {
+ case kNop: return "nop";
+ case kAconst_null: return "aconst_null";
+ case kIconst_m1: return "iconst_m1";
+ case kIconst_0: return "iconst_0";
+ case kIconst_1: return "iconst_1";
+ case kIconst_2: return "iconst_2";
+ case kIconst_3: return "iconst_3";
+ case kIconst_4: return "iconst_4";
+ case kIconst_5: return "iconst_5";
+ case kLconst_0: return "lconst_0";
+ case kLconst_1: return "lconst_1";
+ case kFconst_0: return "fconst_0";
+ case kFconst_1: return "fconst_1";
+ case kFconst_2: return "fconst_2";
+ case kDconst_0: return "dconst_0";
+ case kDconst_1: return "dconst_1";
+ case kBipush: return "bipush";
+ case kSipush: return "sipush";
+ case kLdc: return "ldc";
+ case kLdc_w: return "ldc_w";
+ case kLdc2_w: return "ldc2_w";
+ case kIload: return "iload";
+ case kLload: return "lload";
+ case kFload: return "fload";
+ case kDload: return "dload";
+ case kAload: return "aload";
+ case kIload_0: return "iload_0";
+ case kIload_1: return "iload_1";
+ case kIload_2: return "iload_2";
+ case kIload_3: return "iload_3";
+ case kLload_0: return "lload_0";
+ case kLload_1: return "lload_1";
+ case kLload_2: return "lload_2";
+ case kLload_3: return "lload_3";
+ case kFload_0: return "fload_0";
+ case kFload_1: return "fload_1";
+ case kFload_2: return "fload_2";
+ case kFload_3: return "fload_3";
+ case kDload_0: return "dload_0";
+ case kDload_1: return "dload_1";
+ case kDload_2: return "dload_2";
+ case kDload_3: return "dload_3";
+ case kAload_0: return "aload_0";
+ case kAload_1: return "aload_1";
+ case kAload_2: return "aload_2";
+ case kAload_3: return "aload_3";
+ case kIaload: return "iaload";
+ case kLaload: return "laload";
+ case kFaload: return "faload";
+ case kDaload: return "daload";
+ case kAaload: return "aaload";
+ case kBaload: return "baload";
+ case kCaload: return "caload";
+ case kSaload: return "saload";
+ case kIstore: return "istore";
+ case kLstore: return "lstore";
+ case kFstore: return "fstore";
+ case kDstore: return "dstore";
+ case kAstore: return "astore";
+ case kIstore_0: return "istore_0";
+ case kIstore_1: return "istore_1";
+ case kIstore_2: return "istore_2";
+ case kIstore_3: return "istore_3";
+ case kLstore_0: return "lstore_0";
+ case kLstore_1: return "lstore_1";
+ case kLstore_2: return "lstore_2";
+ case kLstore_3: return "lstore_3";
+ case kFstore_0: return "fstore_0";
+ case kFstore_1: return "fstore_1";
+ case kFstore_2: return "fstore_2";
+ case kFstore_3: return "fstore_3";
+ case kDstore_0: return "dstore_0";
+ case kDstore_1: return "dstore_1";
+ case kDstore_2: return "dstore_2";
+ case kDstore_3: return "dstore_3";
+ case kAstore_0: return "astore_0";
+ case kAstore_1: return "astore_1";
+ case kAstore_2: return "astore_2";
+ case kAstore_3: return "astore_3";
+ case kIastore: return "iastore";
+ case kLastore: return "lastore";
+ case kFastore: return "fastore";
+ case kDastore: return "dastore";
+ case kAastore: return "aastore";
+ case kBastore: return "bastore";
+ case kCastore: return "castore";
+ case kSastore: return "sastore";
+ case kPop: return "pop";
+ case kPop2: return "pop2";
+ case kDup: return "dup";
+ case kDup_x1: return "dup_x1";
+ case kDup_x2: return "dup_x2";
+ case kDup2: return "dup2";
+ case kDup2_x1: return "dup2_x1";
+ case kDup2_x2: return "dup2_x2";
+ case kSwap: return "swap";
+ case kIadd: return "iadd";
+ case kLadd: return "ladd";
+ case kFadd: return "fadd";
+ case kDadd: return "dadd";
+ case kIsub: return "isub";
+ case kLsub: return "lsub";
+ case kFsub: return "fsub";
+ case kDsub: return "dsub";
+ case kImul: return "imul";
+ case kLmul: return "lmul";
+ case kFmul: return "fmul";
+ case kDmul: return "dmul";
+ case kIdiv: return "idiv";
+ case kLdiv: return "ldiv";
+ case kFdiv: return "fdiv";
+ case kDdiv: return "ddiv";
+ case kIrem: return "irem";
+ case kLrem: return "lrem";
+ case kFrem: return "frem";
+ case kDrem: return "drem";
+ case kIneg: return "ineg";
+ case kLneg: return "lneg";
+ case kFneg: return "fneg";
+ case kDneg: return "dneg";
+ case kIshl: return "ishl";
+ case kLshl: return "lshl";
+ case kIshr: return "ishr";
+ case kLshr: return "lshr";
+ case kIushr: return "iushr";
+ case kLushr: return "lushr";
+ case kIand: return "iand";
+ case kLand: return "land";
+ case kIor: return "ior";
+ case kLor: return "lor";
+ case kIxor: return "ixor";
+ case kLxor: return "lxor";
+ case kIinc: return "iinc";
+ case kI2l: return "i2l";
+ case kI2f: return "i2f";
+ case kI2d: return "i2d";
+ case kL2i: return "l2i";
+ case kL2f: return "l2f";
+ case kL2d: return "l2d";
+ case kF2i: return "f2i";
+ case kF2l: return "f2l";
+ case kF2d: return "f2d";
+ case kD2i: return "d2i";
+ case kD2l: return "d2l";
+ case kD2f: return "d2f";
+ case kI2b: return "i2b";
+ case kI2c: return "i2c";
+ case kI2s: return "i2s";
+ case kLcmp: return "lcmp";
+ case kFcmpl: return "fcmpl";
+ case kFcmpg: return "fcmpg";
+ case kDcmpl: return "dcmpl";
+ case kDcmpg: return "dcmpg";
+ case kIfeq: return "ifeq";
+ case kIfne: return "ifne";
+ case kIflt: return "iflt";
+ case kIfge: return "ifge";
+ case kIfgt: return "ifgt";
+ case kIfle: return "ifle";
+ case kIf_icmpeq: return "if_icmpeq";
+ case kIf_icmpne: return "if_icmpne";
+ case kIf_icmplt: return "if_icmplt";
+ case kIf_icmpge: return "if_icmpge";
+ case kIf_icmpgt: return "if_icmpgt";
+ case kIf_icmple: return "if_icmple";
+ case kIf_acmpeq: return "if_acmpeq";
+ case kIf_acmpne: return "if_acmpne";
+ case kGoto: return "goto";
+ case kJsr: return "jsr";
+ case kRet: return "ret";
+ case kTableswitch: return "tableswitch";
+ case kLookupswitch: return "lookupswitch";
+ case kIreturn: return "ireturn";
+ case kLreturn: return "lreturn";
+ case kFreturn: return "freturn";
+ case kDreturn: return "dreturn";
+ case kAreturn: return "areturn";
+ case kReturn: return "return";
+ case kGetstatic: return "getstatic";
+ case kPutstatic: return "putstatic";
+ case kGetfield: return "getfield";
+ case kPutfield: return "putfield";
+ case kInvokevirtual: return "invokevirtual";
+ case kInvokespecial: return "invokespecial";
+ case kInvokestatic: return "invokestatic";
+ case kInvokeinterface: return "invokeinterface";
+ case kInvokedynamic: return "invokedynamic";
+ case kNew: return "new";
+ case kNewarray: return "newarray";
+ case kAnewarray: return "anewarray";
+ case kArraylength: return "arraylength";
+ case kAthrow: return "athrow";
+ case kCheckcast: return "checkcast";
+ case kInstanceof: return "instanceof";
+ case kMonitorenter: return "monitorenter";
+ case kMonitorexit: return "monitorexit";
+ case kWide: return "wide";
+ case kMultianewarray: return "multianewarray";
+ case kIfnull: return "ifnull";
+ case kIfnonnull: return "ifnonnull";
+ case kGoto_w: return "goto_w";
+ case kJsr_w: return "jsr_w";
+ case kBreakpoint: return "breakpoint";
+ case kImpdep1: return "impdep1";
+ case kImpdep2: return "impdep2";
+ default: LOG(FATAL) << "Unknown opcode " << op;
+ }
+ return "";
+ }
+ };
+};
+
+class DexInstructionDecoder : public InstructionDecoder {
+ public:
+ virtual size_t GetMaximumOpcode() override {
+ return 0xff;
+ }
+
+ virtual const char* GetName(size_t opcode) override {
+ Bytecode::Opcode op = static_cast<Bytecode::Opcode>(opcode);
+ return Bytecode::ToString(op);
+ }
+
+ virtual size_t LocationToOffset(size_t j_location) {
+ // dex pc is uint16_t*, but offset needs to be in bytes.
+ return j_location * (sizeof(uint16_t) / sizeof(uint8_t));
+ }
+
+ private:
+ class Bytecode {
+ public:
+ enum Opcode {
+#define MAKE_ENUM_DEFINITION(opcode, instruction_code, name, format, index, flags, extended_flags, verifier_flags) \
+ instruction_code = opcode,
+DEX_INSTRUCTION_LIST(MAKE_ENUM_DEFINITION)
+#undef MAKE_ENUM_DEFINITION
+ };
+
+ static_assert(static_cast<uint32_t>(Bytecode::Opcode::NOP) == 0, "");
+ static_assert(static_cast<uint32_t>(Bytecode::Opcode::MOVE) == 1, "");
+
+ static const char* ToString(Bytecode::Opcode op) {
+ switch (op) {
+#define MAKE_ENUM_DEFINITION(opcode, instruction_code, name, format, index, flags, extended_flags, verifier_flags) \
+ case instruction_code: return (name);
+DEX_INSTRUCTION_LIST(MAKE_ENUM_DEFINITION)
+#undef MAKE_ENUM_DEFINITION
+ default: LOG(FATAL) << "Unknown opcode " << op;
+ }
+ return "";
+ }
+ };
+};
+
+InstructionDecoder* InstructionDecoder::NewInstance(InstructionFileFormat file_format) {
+ switch (file_format) {
+ case InstructionFileFormat::kClass:
+ return new ClassInstructionDecoder();
+ case InstructionFileFormat::kDex:
+ return new DexInstructionDecoder();
+ default:
+ return nullptr;
+ }
+}
+} // namespace titrace
diff --git a/tools/titrace/instruction_decoder.h b/tools/titrace/instruction_decoder.h
new file mode 100644
index 0000000000..34be2e903d
--- /dev/null
+++ b/tools/titrace/instruction_decoder.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2017 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.
+//
+
+
+#ifndef ART_TOOLS_TITRACE_INSTRUCTION_DECODER_H_
+#define ART_TOOLS_TITRACE_INSTRUCTION_DECODER_H_
+
+#include <stddef.h>
+
+namespace titrace {
+
+enum class InstructionFileFormat {
+ kClass,
+ kDex
+};
+
+class InstructionDecoder {
+ public:
+ virtual size_t GetMaximumOpcode() = 0;
+ virtual const char* GetName(size_t opcode) = 0;
+ virtual size_t LocationToOffset(size_t j_location) = 0;
+
+ virtual ~InstructionDecoder() {}
+
+ static InstructionDecoder* NewInstance(InstructionFileFormat file_format);
+};
+
+} // namespace titrace
+
+#endif // ART_TOOLS_TITRACE_INSTRUCTION_DECODER_H_
diff --git a/tools/titrace/titrace.cc b/tools/titrace/titrace.cc
new file mode 100644
index 0000000000..bf1218b006
--- /dev/null
+++ b/tools/titrace/titrace.cc
@@ -0,0 +1,309 @@
+// Copyright (C) 2017 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.
+//
+
+#include "instruction_decoder.h"
+
+#include <android-base/logging.h>
+#include <atomic>
+#include <jni.h>
+#include <jvmti.h>
+#include <map>
+#include <memory>
+#include <mutex>
+
+// We could probably return a JNI_ERR here but lets crash instead if something fails.
+#define CHECK_JVMTI_ERROR(jvmti, errnum) \
+ CHECK_EQ(JVMTI_ERROR_NONE, (errnum)) << GetJvmtiErrorString((jvmti), (errnum)) << (" ")
+
+namespace titrace {
+
+static const char* GetJvmtiErrorString(jvmtiEnv* jvmti, jvmtiError errnum) {
+ char* errnum_str = nullptr;
+ jvmti->GetErrorName(errnum, /*out*/ &errnum_str);
+ if (errnum_str == nullptr) {
+ return "Unknown";
+ }
+
+ return errnum_str;
+}
+
+// Type-safe wrapper for JVMTI-allocated memory.
+// Deallocates with jvmtiEnv::Deallocate.
+template <typename T>
+struct TiMemory {
+ explicit TiMemory(jvmtiEnv* env, T* mem, size_t size) : env_(env), mem_(mem), size_(size) {
+ }
+
+ ~TiMemory() {
+ if (mem_ != nullptr) {
+ env_->Deallocate(static_cast<unsigned char*>(mem_));
+ }
+ mem_ = nullptr;
+ }
+
+ TiMemory(const TiMemory& other) = delete;
+ TiMemory(TiMemory&& other) {
+ env_ = other.env_;
+ mem_ = other.mem_;
+ size_ = other.size_;
+
+ if (this != &other) {
+ other.env_ = nullptr;
+ other.mem_ = nullptr;
+ other.size_ = 0u;
+ }
+ }
+
+ TiMemory& operator=(TiMemory&& other) {
+ if (mem_ != other.mem_) {
+ TiMemory::~TiMemory();
+ }
+ new (this) TiMemory(std::move(other));
+ return *this;
+ }
+
+ T* GetMemory() {
+ return mem_;
+ }
+
+ size_t Size() {
+ return size_ / sizeof(T);
+ }
+
+ private:
+ jvmtiEnv* env_;
+ T* mem_;
+ size_t size_;
+};
+
+struct MethodBytecode {
+ explicit MethodBytecode(jvmtiEnv* env, unsigned char* memory, jint size)
+ : bytecode_(env, memory, static_cast<size_t>(size)) {
+ }
+
+ TiMemory<uint8_t> bytecode_;
+};
+
+struct TraceStatistics {
+ static void Initialize(jvmtiEnv* jvmti) {
+ TraceStatistics& stats = GetSingleton();
+
+ bool is_ri = true;
+ {
+ jvmtiError error;
+ char* value_ptr;
+ error = jvmti->GetSystemProperty("java.vm.name", /*out*/ &value_ptr);
+ CHECK_JVMTI_ERROR(jvmti, error) << "Failed to get property 'java.vm.name'";
+ CHECK(value_ptr != nullptr) << "Returned property was null for 'java.vm.name'";
+
+ if (strcmp("Dalvik", value_ptr) == 0) {
+ is_ri = false;
+ }
+ }
+
+ InstructionFileFormat format =
+ is_ri ? InstructionFileFormat::kClass : InstructionFileFormat::kDex;
+ stats.instruction_decoder_.reset(InstructionDecoder::NewInstance(format));
+
+ CHECK_GE(arraysize(stats.instruction_counter_),
+ stats.instruction_decoder_->GetMaximumOpcode());
+ }
+
+ static TraceStatistics& GetSingleton() {
+ static TraceStatistics stats;
+ return stats;
+ }
+
+ void Log() {
+ LOG(INFO) << "================================================";
+ LOG(INFO) << " TI Trace // Summary ";
+ LOG(INFO) << "++++++++++++++++++++++++++++++++++++++++++++++++";
+ LOG(INFO) << " * Single step counter: " << single_step_counter_;
+ LOG(INFO) << "+++++++++++ Instructions Count ++++++++++++";
+
+ size_t total = single_step_counter_;
+ for (size_t i = 0; i < arraysize(instruction_counter_); ++i) {
+ size_t inst_count = instruction_counter_[i];
+ if (inst_count > 0) {
+ const char* opcode_name = instruction_decoder_->GetName(i);
+ LOG(INFO) << " * " << opcode_name << "(op:" << i << "), count: " << inst_count
+ << ", % of total: " << (100.0 * inst_count / total);
+ }
+ }
+
+ LOG(INFO) << "------------------------------------------------";
+ }
+
+ void OnSingleStep(jvmtiEnv* jvmti_env, jmethodID method, jlocation location) {
+ // Counters do not need a happens-before.
+ // Use the weakest memory order simply to avoid tearing.
+ single_step_counter_.fetch_add(1u, std::memory_order_relaxed);
+
+ MethodBytecode& bytecode = LookupBytecode(jvmti_env, method);
+
+ // Decode jlocation value that depends on the bytecode format.
+ size_t actual_location = instruction_decoder_->LocationToOffset(static_cast<size_t>(location));
+
+ // Decode the exact instruction and increment its counter.
+ CHECK_LE(actual_location, bytecode.bytecode_.Size());
+ RecordInstruction(bytecode.bytecode_.GetMemory() + actual_location);
+ }
+
+ private:
+ void RecordInstruction(const uint8_t* instruction) {
+ uint8_t opcode = instruction[0];
+ // Counters do not need a happens-before.
+ // Use the weakest memory order simply to avoid tearing.
+ instruction_counter_[opcode].fetch_add(1u, std::memory_order_relaxed);
+ }
+
+ MethodBytecode& LookupBytecode(jvmtiEnv* jvmti_env, jmethodID method) {
+ jvmtiError error;
+ std::lock_guard<std::mutex> lock(bytecode_cache_mutex_);
+
+ auto it = bytecode_cache_.find(method);
+ if (it == bytecode_cache_.end()) {
+ jint bytecode_count_ptr = 0;
+ unsigned char* bytecodes_ptr = nullptr;
+
+ error = jvmti_env->GetBytecodes(method, &bytecode_count_ptr, &bytecodes_ptr);
+ CHECK_JVMTI_ERROR(jvmti_env, error) << "Failed to get bytecodes for method " << method;
+ CHECK(bytecodes_ptr != nullptr) << "Bytecode ptr was null for method " << method;
+ CHECK_GE(bytecode_count_ptr, 0) << "Bytecode size too small for method " << method;
+
+ // std::pair<iterator, bool inserted>
+ auto&& pair = bytecode_cache_.insert(
+ std::make_pair(method, MethodBytecode(jvmti_env, bytecodes_ptr, bytecode_count_ptr)));
+ it = pair.first;
+ }
+
+ // Returning the address is safe. if map is resized, the contents will not move.
+ return it->second;
+ }
+
+ std::unique_ptr<InstructionDecoder> instruction_decoder_;
+
+ std::atomic<size_t> single_step_counter_{0u}; // NOLINT [readability/braces] [4] [whitespace/braces] [5]
+ std::atomic<size_t> instruction_counter_[256]{}; // NOLINT [whitespace/braces] [5]
+
+ // Cache the bytecode to avoid calling into JVMTI repeatedly.
+ // TODO: invalidate if the bytecode was updated?
+ std::map<jmethodID, MethodBytecode> bytecode_cache_;
+ // bytecode cache is thread-safe.
+ std::mutex bytecode_cache_mutex_;
+};
+
+struct EventCallbacks {
+ static void SingleStep(jvmtiEnv* jvmti_env,
+ JNIEnv* jni_env ATTRIBUTE_UNUSED,
+ jthread thread ATTRIBUTE_UNUSED,
+ jmethodID method,
+ jlocation location) {
+ TraceStatistics& stats = TraceStatistics::GetSingleton();
+ stats.OnSingleStep(jvmti_env, method, location);
+ }
+
+ // Use "kill -SIGQUIT" to generate a data dump request.
+ // Useful when running an android app since it doesn't go through
+ // a normal Agent_OnUnload.
+ static void DataDumpRequest(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED) {
+ TraceStatistics& stats = TraceStatistics::GetSingleton();
+ stats.Log();
+ }
+};
+
+} // namespace titrace
+
+// Late attachment (e.g. 'am attach-agent').
+JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char* options, void* reserved) {
+ return Agent_OnLoad(vm, options, reserved);
+}
+
+// Early attachment (e.g. 'java -agent[lib|path]:filename.so').
+JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm,
+ char* /* options */,
+ void* /* reserved */) {
+ using namespace titrace; // NOLINT [build/namespaces] [5]
+
+ android::base::InitLogging(/* argv */nullptr);
+
+ jvmtiEnv* jvmti = nullptr;
+ {
+ jint res = 0;
+ res = jvm->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_1);
+
+ if (res != JNI_OK || jvmti == nullptr) {
+ LOG(FATAL) << "Unable to access JVMTI, error code " << res;
+ }
+ }
+
+ LOG(INFO) << "Agent_OnLoad: Hello World";
+
+ {
+ // Initialize our instruction file-format decoder.
+ TraceStatistics::Initialize(jvmti);
+ }
+
+ jvmtiError error{}; // NOLINT [readability/braces] [4] [whitespace/braces] [5]
+
+ // Set capabilities.
+ {
+ jvmtiCapabilities caps = {};
+ caps.can_generate_single_step_events = 1;
+ caps.can_get_bytecodes = 1;
+
+ error = jvmti->AddCapabilities(&caps);
+ CHECK_JVMTI_ERROR(jvmti, error)
+ << "Unable to get necessary JVMTI capabilities";
+ }
+
+ // Set callbacks.
+ {
+ jvmtiEventCallbacks callbacks = {};
+ callbacks.SingleStep = &EventCallbacks::SingleStep;
+ callbacks.DataDumpRequest = &EventCallbacks::DataDumpRequest;
+
+ error = jvmti->SetEventCallbacks(&callbacks,
+ static_cast<jint>(sizeof(callbacks)));
+ CHECK_JVMTI_ERROR(jvmti, error) << "Unable to set event callbacks";
+ }
+
+ // Enable events notification.
+ {
+ error = jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_SINGLE_STEP,
+ nullptr /* all threads */);
+ CHECK_JVMTI_ERROR(jvmti, error)
+ << "Failed to enable SINGLE_STEP notification";
+
+ error = jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_DATA_DUMP_REQUEST,
+ nullptr /* all threads */);
+ CHECK_JVMTI_ERROR(jvmti, error)
+ << "Failed to enable DATA_DUMP_REQUEST notification";
+ }
+
+ return JNI_OK;
+}
+
+// Note: This is not called for normal Android apps,
+// use "kill -SIGQUIT" instead to generate a data dump request.
+JNIEXPORT void JNICALL Agent_OnUnload(JavaVM* vm ATTRIBUTE_UNUSED) {
+ using namespace titrace; // NOLINT [build/namespaces] [5]
+ LOG(INFO) << "Agent_OnUnload: Goodbye";
+
+ TraceStatistics::GetSingleton().Log();
+}
+