blob: 7adcd4ab78faa3a590e7c68c3722ee1d3a4d9df2 [file] [log] [blame]
/*
* 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.
*/
package art;
import java.lang.reflect.Field;
import org.apache.harmony.dalvik.ddmc.*;
import dalvik.system.VMDebug;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.*;
import java.util.zip.Adler32;
import java.nio.*;
public class Test1940 {
public static final int DDMS_TYPE_INDEX = 0;
public static final int DDMS_LEN_INDEX = 4;
public static final int DDMS_HEADER_LENGTH = 8;
public static final int MY_DDMS_TYPE = 0xDEADBEEF;
public static final int MY_DDMS_RESPONSE_TYPE = 0xFADE7357;
public static final int MY_EMPTY_DDMS_TYPE = 0xABCDEF01;
public static final int MY_INVALID_DDMS_TYPE = 0x12345678;
public static final boolean PRINT_ALL_CHUNKS = false;
public static interface DdmHandler {
public void HandleChunk(int type, byte[] data) throws Exception;
}
public static final class TestError extends Error {
public TestError(String s) { super(s); }
}
private static void checkEq(Object a, Object b) {
if (!a.equals(b)) {
throw new TestError("Failure: " + a + " != " + b);
}
}
private static boolean chunkEq(Chunk c1, Chunk c2) {
ChunkWrapper a = new ChunkWrapper(c1);
ChunkWrapper b = new ChunkWrapper(c2);
return a.type() == b.type() &&
a.offset() == b.offset() &&
a.length() == b.length() &&
Arrays.equals(a.data(), b.data());
}
private static String printChunk(Chunk k) {
ChunkWrapper c = new ChunkWrapper(k);
byte[] out = new byte[c.length()];
System.arraycopy(c.data(), c.offset(), out, 0, c.length());
return String.format("Chunk(Type: 0x%X, Len: %d, data: %s)",
c.type(), c.length(), Arrays.toString(out));
}
private static final class MyDdmHandler extends ChunkHandler {
public void onConnected() {}
public void onDisconnected() {}
public Chunk handleChunk(Chunk req) {
System.out.println("MyDdmHandler: Chunk received: " + printChunk(req));
if (req.type == MY_DDMS_TYPE) {
// For this test we will simply calculate the checksum
ByteBuffer b = ByteBuffer.wrap(new byte[8]);
Adler32 a = new Adler32();
ChunkWrapper reqWrapper = new ChunkWrapper(req);
a.update(reqWrapper.data(), reqWrapper.offset(), reqWrapper.length());
b.order(ByteOrder.BIG_ENDIAN);
long val = a.getValue();
b.putLong(val);
System.out.printf("MyDdmHandler: Putting value 0x%X\n", val);
Chunk ret = new Chunk(MY_DDMS_RESPONSE_TYPE, b.array(), 0, 8);
System.out.println("MyDdmHandler: Chunk returned: " + printChunk(ret));
return ret;
} else if (req.type == MY_EMPTY_DDMS_TYPE) {
return new Chunk(MY_DDMS_RESPONSE_TYPE, new byte[0], 0, 0);
} else if (req.type == MY_INVALID_DDMS_TYPE) {
// This is a very invalid chunk.
return new Chunk(MY_DDMS_RESPONSE_TYPE, new byte[] { 0 }, /*offset*/ 12, /*length*/ 55);
} else {
throw new TestError("Unknown ddm request type: " + req.type);
}
}
}
/**
* Wrapper for accessing the hidden fields in {@link Chunk} in CTS.
*/
private static class ChunkWrapper {
private Chunk c;
ChunkWrapper(Chunk c) {
this.c = c;
}
int type() {
return c.type;
}
int length() {
try {
Field f = Chunk.class.getField("length");
return (int) f.get(c);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
byte[] data() {
try {
Field f = Chunk.class.getField("data");
return (byte[]) f.get(c);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
int offset() {
try {
Field f = Chunk.class.getField("offset");
return (int) f.get(c);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
public static final ChunkHandler SINGLE_HANDLER = new MyDdmHandler();
public static DdmHandler CURRENT_HANDLER;
public static void HandlePublish(int type, byte[] data) throws Exception {
if (PRINT_ALL_CHUNKS) {
System.out.println(
"Unknown Chunk published: " + printChunk(new Chunk(type, data, 0, data.length)));
}
CURRENT_HANDLER.HandleChunk(type, data);
}
// TYPE Thread Create
public static final int TYPE_THCR = 0x54484352;
// Type Thread name
public static final int TYPE_THNM = 0x54484E4D;
// Type Thread death.
public static final int TYPE_THDE = 0x54484445;
// Type Heap info
public static final int TYPE_HPIF = 0x48504946;
// Type Trace Results
public static final int TYPE_MPSE = 0x4D505345;
public static boolean IsFromThread(Thread t, byte[] data) {
// DDMS always puts the thread-id as the first 4 bytes.
ByteBuffer b = ByteBuffer.wrap(data);
b.order(ByteOrder.BIG_ENDIAN);
return b.getInt() == (int) t.getId();
}
public static final class AwaitChunkHandler implements DdmHandler {
public final Predicate<Chunk> needle;
public final DdmHandler chain;
private boolean found = false;
public AwaitChunkHandler(Predicate<Chunk> needle, DdmHandler chain) {
this.needle = needle;
this.chain = chain;
}
public void HandleChunk(int type, byte[] data) throws Exception {
chain.HandleChunk(type, data);
Chunk c = new Chunk(type, data, 0, data.length);
if (needle.test(c)) {
synchronized (this) {
found = true;
notifyAll();
}
}
}
public synchronized void Await() throws Exception {
while (!found) {
wait();
}
}
}
public static void run() throws Exception {
DdmHandler BaseHandler = (type, data) -> {
System.out.println("Chunk published: " + printChunk(new Chunk(type, data, 0, data.length)));
};
CURRENT_HANDLER = BaseHandler;
initializeTest();
Method publish = Test1940.class.getDeclaredMethod("HandlePublish",
Integer.TYPE,
new byte[0].getClass());
Thread listener = new Thread(() -> { Test1940.publishListen(publish); });
listener.setDaemon(true);
listener.start();
// Test sending chunk directly.
DdmServer.registerHandler(MY_DDMS_TYPE, SINGLE_HANDLER);
DdmServer.registerHandler(MY_EMPTY_DDMS_TYPE, SINGLE_HANDLER);
DdmServer.registerHandler(MY_INVALID_DDMS_TYPE, SINGLE_HANDLER);
DdmServer.registrationComplete();
byte[] data = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
System.out.println("Sending data " + Arrays.toString(data));
Chunk res = processChunk(data);
System.out.println("JVMTI returned chunk: " + printChunk(res));
// Test sending an empty chunk.
System.out.println("Sending empty data array");
res = processChunk(new byte[0]);
System.out.println("JVMTI returned chunk: " + printChunk(res));
// Test sending chunk through DdmServer#sendChunk
Chunk c = new Chunk(
MY_DDMS_TYPE, new byte[] { 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 }, 0, 8);
AwaitChunkHandler h = new AwaitChunkHandler((x) -> chunkEq(c, x), CURRENT_HANDLER);
CURRENT_HANDLER = h;
System.out.println("Sending chunk: " + printChunk(c));
DdmServer.sendChunk(c);
h.Await();
CURRENT_HANDLER = BaseHandler;
// Test getting back an empty chunk.
data = new byte[] { 0x1 };
System.out.println(
"Sending data " + Arrays.toString(data) + " to chunk handler " + MY_EMPTY_DDMS_TYPE);
res = processChunk(new Chunk(MY_EMPTY_DDMS_TYPE, data, 0, 1));
System.out.println("JVMTI returned chunk: " + printChunk(res));
// Test getting back an invalid chunk.
System.out.println(
"Sending data " + Arrays.toString(data) + " to chunk handler " + MY_INVALID_DDMS_TYPE);
try {
res = processChunk(new Chunk(MY_INVALID_DDMS_TYPE, data, 0, 1));
System.out.println("JVMTI returned chunk: " + printChunk(res));
} catch (RuntimeException e) {
System.out.println("Got error: " + e.getMessage());
}
// Test thread chunks are sent.
final boolean[] types_seen = new boolean[] { false, false, false };
AwaitChunkHandler wait_thd= new AwaitChunkHandler(
(x) -> types_seen[0] && types_seen[1] && types_seen[2],
(type, cdata) -> {
switch (type) {
case TYPE_THCR:
types_seen[0] = true;
break;
case TYPE_THNM:
types_seen[1] = true;
break;
case TYPE_THDE:
types_seen[2] = true;
break;
default:
// We don't want to print other types.
break;
}
});
CURRENT_HANDLER = wait_thd;
DdmVmInternal.setThreadNotifyEnabled(true);
System.out.println("threadNotify started!");
final Thread thr = new Thread(() -> { return; }, "THREAD");
thr.start();
System.out.println("Target thread started!");
thr.join();
System.out.println("Target thread finished!");
DdmVmInternal.setThreadNotifyEnabled(false);
System.out.println("threadNotify Disabled!");
wait_thd.Await();
// Make sure we saw at least one of Thread-create, Thread name, & thread death.
if (!types_seen[0] || !types_seen[1] || !types_seen[2]) {
System.out.println("Didn't see expected chunks for thread creation! got: " +
Arrays.toString(types_seen));
} else {
System.out.println("Saw expected thread events.");
}
// method Tracing
AwaitChunkHandler mpse = new AwaitChunkHandler((x) -> x.type == TYPE_MPSE, (type, cdata) -> {
// This chunk includes timing and thread information so we just check the type.
if (type == TYPE_MPSE) {
System.out.println("Expected chunk type published: " + type);
}
});
CURRENT_HANDLER = mpse;
VMDebug.startMethodTracingDdms(/*size: default*/0,
/*flags: none*/ 0,
/*sampling*/ false,
/*interval*/ 0);
doNothing();
doNothing();
doNothing();
doNothing();
VMDebug.stopMethodTracing();
mpse.Await();
}
private static void doNothing() {}
private static Chunk processChunk(byte[] val) {
return processChunk(new Chunk(MY_DDMS_TYPE, val, 0, val.length));
}
private static native void initializeTest();
private static native Chunk processChunk(Chunk val);
private static native void publishListen(Method publish);
}