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