1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package art; 18 19 import org.apache.harmony.dalvik.ddmc.*; 20 import dalvik.system.VMDebug; 21 22 import java.lang.reflect.Method; 23 import java.util.Arrays; 24 import java.util.function.*; 25 import java.util.zip.Adler32; 26 import java.nio.*; 27 28 public class Test1940 { 29 public static final int DDMS_TYPE_INDEX = 0; 30 public static final int DDMS_LEN_INDEX = 4; 31 public static final int DDMS_HEADER_LENGTH = 8; 32 public static final int MY_DDMS_TYPE = 0xDEADBEEF; 33 public static final int MY_DDMS_RESPONSE_TYPE = 0xFADE7357; 34 public static final int MY_EMPTY_DDMS_TYPE = 0xABCDEF01; 35 public static final int MY_INVALID_DDMS_TYPE = 0x12345678; 36 37 public static final boolean PRINT_ALL_CHUNKS = false; 38 39 public static interface DdmHandler { HandleChunk(int type, byte[] data)40 public void HandleChunk(int type, byte[] data) throws Exception; 41 } 42 43 public static final class TestError extends Error { TestError(String s)44 public TestError(String s) { super(s); } 45 } 46 checkEq(Object a, Object b)47 private static void checkEq(Object a, Object b) { 48 if (!a.equals(b)) { 49 throw new TestError("Failure: " + a + " != " + b); 50 } 51 } 52 chunkEq(Chunk a, Chunk b)53 private static boolean chunkEq(Chunk a, Chunk b) { 54 return a.type == b.type && 55 a.offset == b.offset && 56 a.length == b.length && 57 Arrays.equals(a.data, b.data); 58 } 59 printChunk(Chunk k)60 private static String printChunk(Chunk k) { 61 byte[] out = new byte[k.length]; 62 System.arraycopy(k.data, k.offset, out, 0, k.length); 63 return String.format("Chunk(Type: 0x%X, Len: %d, data: %s)", 64 k.type, k.length, Arrays.toString(out)); 65 } 66 67 private static final class MyDdmHandler extends ChunkHandler { onConnected()68 public void onConnected() {} onDisconnected()69 public void onDisconnected() {} handleChunk(Chunk req)70 public Chunk handleChunk(Chunk req) { 71 System.out.println("MyDdmHandler: Chunk received: " + printChunk(req)); 72 if (req.type == MY_DDMS_TYPE) { 73 // For this test we will simply calculate the checksum 74 ByteBuffer b = ByteBuffer.wrap(new byte[8]); 75 Adler32 a = new Adler32(); 76 a.update(req.data, req.offset, req.length); 77 b.order(ByteOrder.BIG_ENDIAN); 78 long val = a.getValue(); 79 b.putLong(val); 80 System.out.printf("MyDdmHandler: Putting value 0x%X\n", val); 81 Chunk ret = new Chunk(MY_DDMS_RESPONSE_TYPE, b.array(), 0, 8); 82 System.out.println("MyDdmHandler: Chunk returned: " + printChunk(ret)); 83 return ret; 84 } else if (req.type == MY_EMPTY_DDMS_TYPE) { 85 return new Chunk(MY_DDMS_RESPONSE_TYPE, new byte[0], 0, 0); 86 } else if (req.type == MY_INVALID_DDMS_TYPE) { 87 // This is a very invalid chunk. 88 return new Chunk(MY_DDMS_RESPONSE_TYPE, new byte[] { 0 }, /*offset*/ 12, /*length*/ 55); 89 } else { 90 throw new TestError("Unknown ddm request type: " + req.type); 91 } 92 } 93 } 94 95 public static final ChunkHandler SINGLE_HANDLER = new MyDdmHandler(); 96 97 public static DdmHandler CURRENT_HANDLER; 98 HandlePublish(int type, byte[] data)99 public static void HandlePublish(int type, byte[] data) throws Exception { 100 if (PRINT_ALL_CHUNKS) { 101 System.out.println( 102 "Unknown Chunk published: " + printChunk(new Chunk(type, data, 0, data.length))); 103 } 104 CURRENT_HANDLER.HandleChunk(type, data); 105 } 106 107 // TYPE Thread Create 108 public static final int TYPE_THCR = 0x54484352; 109 // Type Thread name 110 public static final int TYPE_THNM = 0x54484E4D; 111 // Type Thread death. 112 public static final int TYPE_THDE = 0x54484445; 113 // Type Heap info 114 public static final int TYPE_HPIF = 0x48504946; 115 // Type Trace Results 116 public static final int TYPE_MPSE = 0x4D505345; 117 IsFromThread(Thread t, byte[] data)118 public static boolean IsFromThread(Thread t, byte[] data) { 119 // DDMS always puts the thread-id as the first 4 bytes. 120 ByteBuffer b = ByteBuffer.wrap(data); 121 b.order(ByteOrder.BIG_ENDIAN); 122 return b.getInt() == (int) t.getId(); 123 } 124 125 public static final class AwaitChunkHandler implements DdmHandler { 126 public final Predicate<Chunk> needle; 127 public final DdmHandler chain; 128 private boolean found = false; AwaitChunkHandler(Predicate<Chunk> needle, DdmHandler chain)129 public AwaitChunkHandler(Predicate<Chunk> needle, DdmHandler chain) { 130 this.needle = needle; 131 this.chain = chain; 132 } HandleChunk(int type, byte[] data)133 public void HandleChunk(int type, byte[] data) throws Exception { 134 chain.HandleChunk(type, data); 135 Chunk c = new Chunk(type, data, 0, data.length); 136 if (needle.test(c)) { 137 synchronized (this) { 138 found = true; 139 notifyAll(); 140 } 141 } 142 } Await()143 public synchronized void Await() throws Exception { 144 while (!found) { 145 wait(); 146 } 147 } 148 } 149 run()150 public static void run() throws Exception { 151 DdmHandler BaseHandler = (type, data) -> { 152 System.out.println("Chunk published: " + printChunk(new Chunk(type, data, 0, data.length))); 153 }; 154 CURRENT_HANDLER = BaseHandler; 155 initializeTest(); 156 Method publish = Test1940.class.getDeclaredMethod("HandlePublish", 157 Integer.TYPE, 158 new byte[0].getClass()); 159 Thread listener = new Thread(() -> { Test1940.publishListen(publish); }); 160 listener.setDaemon(true); 161 listener.start(); 162 // Test sending chunk directly. 163 DdmServer.registerHandler(MY_DDMS_TYPE, SINGLE_HANDLER); 164 DdmServer.registerHandler(MY_EMPTY_DDMS_TYPE, SINGLE_HANDLER); 165 DdmServer.registerHandler(MY_INVALID_DDMS_TYPE, SINGLE_HANDLER); 166 DdmServer.registrationComplete(); 167 byte[] data = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; 168 System.out.println("Sending data " + Arrays.toString(data)); 169 Chunk res = processChunk(data); 170 System.out.println("JVMTI returned chunk: " + printChunk(res)); 171 172 // Test sending an empty chunk. 173 System.out.println("Sending empty data array"); 174 res = processChunk(new byte[0]); 175 System.out.println("JVMTI returned chunk: " + printChunk(res)); 176 177 // Test sending chunk through DdmServer#sendChunk 178 Chunk c = new Chunk( 179 MY_DDMS_TYPE, new byte[] { 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 }, 0, 8); 180 AwaitChunkHandler h = new AwaitChunkHandler((x) -> chunkEq(c, x), CURRENT_HANDLER); 181 CURRENT_HANDLER = h; 182 System.out.println("Sending chunk: " + printChunk(c)); 183 DdmServer.sendChunk(c); 184 h.Await(); 185 CURRENT_HANDLER = BaseHandler; 186 187 // Test getting back an empty chunk. 188 data = new byte[] { 0x1 }; 189 System.out.println( 190 "Sending data " + Arrays.toString(data) + " to chunk handler " + MY_EMPTY_DDMS_TYPE); 191 res = processChunk(new Chunk(MY_EMPTY_DDMS_TYPE, data, 0, 1)); 192 System.out.println("JVMTI returned chunk: " + printChunk(res)); 193 194 // Test getting back an invalid chunk. 195 System.out.println( 196 "Sending data " + Arrays.toString(data) + " to chunk handler " + MY_INVALID_DDMS_TYPE); 197 try { 198 res = processChunk(new Chunk(MY_INVALID_DDMS_TYPE, data, 0, 1)); 199 System.out.println("JVMTI returned chunk: " + printChunk(res)); 200 } catch (RuntimeException e) { 201 System.out.println("Got error: " + e.getMessage()); 202 } 203 204 // Test thread chunks are sent. 205 final boolean[] types_seen = new boolean[] { false, false, false }; 206 AwaitChunkHandler wait_thd= new AwaitChunkHandler( 207 (x) -> types_seen[0] && types_seen[1] && types_seen[2], 208 (type, cdata) -> { 209 switch (type) { 210 case TYPE_THCR: 211 types_seen[0] = true; 212 break; 213 case TYPE_THNM: 214 types_seen[1] = true; 215 break; 216 case TYPE_THDE: 217 types_seen[2] = true; 218 break; 219 default: 220 // We don't want to print other types. 221 break; 222 } 223 }); 224 CURRENT_HANDLER = wait_thd; 225 DdmVmInternal.setThreadNotifyEnabled(true); 226 System.out.println("threadNotify started!"); 227 final Thread thr = new Thread(() -> { return; }, "THREAD"); 228 thr.start(); 229 System.out.println("Target thread started!"); 230 thr.join(); 231 System.out.println("Target thread finished!"); 232 DdmVmInternal.setThreadNotifyEnabled(false); 233 System.out.println("threadNotify Disabled!"); 234 wait_thd.Await(); 235 // Make sure we saw at least one of Thread-create, Thread name, & thread death. 236 if (!types_seen[0] || !types_seen[1] || !types_seen[2]) { 237 System.out.println("Didn't see expected chunks for thread creation! got: " + 238 Arrays.toString(types_seen)); 239 } else { 240 System.out.println("Saw expected thread events."); 241 } 242 243 // method Tracing 244 AwaitChunkHandler mpse = new AwaitChunkHandler((x) -> x.type == TYPE_MPSE, (type, cdata) -> { 245 // This chunk includes timing and thread information so we just check the type. 246 if (type == TYPE_MPSE) { 247 System.out.println("Expected chunk type published: " + type); 248 } 249 }); 250 CURRENT_HANDLER = mpse; 251 VMDebug.startMethodTracingDdms(/*size: default*/0, 252 /*flags: none*/ 0, 253 /*sampling*/ false, 254 /*interval*/ 0); 255 doNothing(); 256 doNothing(); 257 doNothing(); 258 doNothing(); 259 VMDebug.stopMethodTracing(); 260 mpse.Await(); 261 } 262 doNothing()263 private static void doNothing() {} processChunk(byte[] val)264 private static Chunk processChunk(byte[] val) { 265 return processChunk(new Chunk(MY_DDMS_TYPE, val, 0, val.length)); 266 } 267 initializeTest()268 private static native void initializeTest(); processChunk(Chunk val)269 private static native Chunk processChunk(Chunk val); publishListen(Method publish)270 private static native void publishListen(Method publish); 271 } 272