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