diff options
author | 2019-05-20 10:04:44 -0700 | |
---|---|---|
committer | 2019-05-22 00:29:38 +0000 | |
commit | e48fd0b4780efadc6b3433fe7a56aa5be2a84325 (patch) | |
tree | 5056001e02d8c4494b643c8d53fd32621e127c1c /test/1966-get-set-local-objects-no-table/src | |
parent | abdb4592fa28d6e75f1160f01cde58ad7c3fef37 (diff) |
Add verifier fallback for JVMTI Get/SetLocalVariable
The JVMTI Get/SetLocalVariable functions used to rely entirely on the
Dex DebugInfo to determine the types of each of the registers. This
could lead to problems since, to prevent possible stack corruption, we
would not allow stack modification if the data was not present.
In order to remove this restriction we will instead make use of the
method verifier to ensure the modification is sensible when the
DebugInfo is not present. Since reconstructing this information using
the verifier is quite slow (compared to reading it from a table) we
will only do this when the table is missing.
Since the verifier lacks some of the information available when
creating the DebugLocalInfo table some semantics will change depending
on if the table is present or not.
- When the DebugLocalInfo table is not present we cannot always
distinguish between floats, ints, and other single-register
primitive types. For simplicity all single-register primitive
types can be modified and read by both the Float and Int versions
of the local variable functions.
- Similarly we cannot always distinguish between long and double
variables.
- Reference types are checked against what the verifier thinks they
need to be according to type unification. This might be more or
less specific than the types recorded in the functions source code.
- Constant int/float '0' values and 'null' cannot always be
differentiated by the verifier. Therefore, one may not always be
able to modify some null or constant 0 registers.
Test: ./test.py --host
Bug: 131711256
Change-Id: I1c9d857ccdec752bfd4ebad76cc9ad96e143866c
Diffstat (limited to 'test/1966-get-set-local-objects-no-table/src')
6 files changed, 262 insertions, 0 deletions
diff --git a/test/1966-get-set-local-objects-no-table/src/Main.java b/test/1966-get-set-local-objects-no-table/src/Main.java new file mode 100644 index 0000000000..198f319421 --- /dev/null +++ b/test/1966-get-set-local-objects-no-table/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1966.run(); + } +} diff --git a/test/1966-get-set-local-objects-no-table/src/art/Breakpoint.java b/test/1966-get-set-local-objects-no-table/src/art/Breakpoint.java new file mode 120000 index 0000000000..3673916cc6 --- /dev/null +++ b/test/1966-get-set-local-objects-no-table/src/art/Breakpoint.java @@ -0,0 +1 @@ +../../../jvmti-common/Breakpoint.java
\ No newline at end of file diff --git a/test/1966-get-set-local-objects-no-table/src/art/Locals.java b/test/1966-get-set-local-objects-no-table/src/art/Locals.java new file mode 120000 index 0000000000..29983862bc --- /dev/null +++ b/test/1966-get-set-local-objects-no-table/src/art/Locals.java @@ -0,0 +1 @@ +../../../jvmti-common/Locals.java
\ No newline at end of file diff --git a/test/1966-get-set-local-objects-no-table/src/art/StackTrace.java b/test/1966-get-set-local-objects-no-table/src/art/StackTrace.java new file mode 120000 index 0000000000..e1a08aadbd --- /dev/null +++ b/test/1966-get-set-local-objects-no-table/src/art/StackTrace.java @@ -0,0 +1 @@ +../../../jvmti-common/StackTrace.java
\ No newline at end of file diff --git a/test/1966-get-set-local-objects-no-table/src/art/Suspension.java b/test/1966-get-set-local-objects-no-table/src/art/Suspension.java new file mode 120000 index 0000000000..bcef96f69d --- /dev/null +++ b/test/1966-get-set-local-objects-no-table/src/art/Suspension.java @@ -0,0 +1 @@ +../../../jvmti-common/Suspension.java
\ No newline at end of file diff --git a/test/1966-get-set-local-objects-no-table/src/art/Test1966.java b/test/1966-get-set-local-objects-no-table/src/art/Test1966.java new file mode 100644 index 0000000000..00f3c4ed91 --- /dev/null +++ b/test/1966-get-set-local-objects-no-table/src/art/Test1966.java @@ -0,0 +1,237 @@ +/* + * 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.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Semaphore; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntConsumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public class Test1966 { + public static final String TARGET_VAR = "TARGET"; + + public static interface TestInterface { + public default void doNothing() {} + } + public static class TestClass1 implements TestInterface { + public String id; + public TestClass1(String id) { + this.id = id; + } + public String toString() { + return String.format("TestClass1(\"%s\")", id); + } + + public static TestInterface createInterface(String id) { + return new TestClass1(id); + } + public static TestClass1 createExact(String id) { + return new TestClass1(id); + } + public static Object create(String id) { + return new TestClass1(id); + } + } + + public static class TestClass1ext extends TestClass1 { + public TestClass1ext(String id) { + super(id); + } + public String toString() { + return String.format("TestClass1ext(\"%s\")", super.toString()); + } + } + public static class TestClass2 { + public String id; + public TestClass2(String id) { + this.id = id; + } + public String toString() { + return String.format("TestClass2(\"%s\")", id); + } + } + public static class TestClass2impl extends TestClass2 implements TestInterface { + public TestClass2impl(String id) { + super(id); + } + public String toString() { + return String.format("TestClass2impl(\"%s\")", super.toString()); + } + } + + public static void reportValue(Object val) { + System.out.println("\tValue is '" + val + + "' (class: " + (val != null ? val.getClass() : "NULL") + ")"); + } + + public static interface SafepointFunction { + public void invoke(Thread thread, Method target, int slot, int depth) throws Exception; + } + + public static interface SetterFunction { + public void SetVar(Thread t, int depth, int slot, Object v); + } + + public static interface GetterFunction { public Object GetVar(Thread t, int depth, int slot); } + + public static SafepointFunction + NamedSet(final String type, final SetterFunction get, final Object v) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, int slot, int depth) { + try { + get.SetVar(t, depth, slot, v); + System.out.println(this + " on " + method + " set value: " + v); + } catch (Exception e) { + System.out.println(this + " on " + method + " failed to set value " + v + " due to " + + e.getMessage()); + } + } + public String toString() { + return "\"Set" + type + "\""; + } + }; + } + + public static SafepointFunction NamedGet(final String type, final GetterFunction get) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, int slot, int depth) { + try { + Object res = get.GetVar(t, depth, slot); + System.out.println(this + " on " + method + " got value: " + res); + } catch (Exception e) { + System.out.println(this + " on " + method + " failed due to " + e.getMessage()); + } + } + public String toString() { + return "\"Get" + type + "\""; + } + }; + } + + public static class TestCase { + public final Method target; + + public TestCase(Method target) { + this.target = target; + } + + public static class ThreadPauser implements IntConsumer { + public final Semaphore sem_wakeup_main; + public final Semaphore sem_wait; + public int slot = -1; + + public ThreadPauser() { + sem_wakeup_main = new Semaphore(0); + sem_wait = new Semaphore(0); + } + + public void accept(int i) { + try { + slot = i; + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public void exec(final SafepointFunction safepoint) throws Exception { + System.out.println("Running " + target + " with " + safepoint + " on remote thread."); + final ThreadPauser pause = new ThreadPauser(); + Thread remote = new Thread(() -> { + try { + target.invoke(null, pause); + } catch (Exception e) { + throw new Error("Error invoking remote thread " + Thread.currentThread(), e); + } + }, "remote thread for " + target + " with " + safepoint); + remote.start(); + pause.waitForOtherThreadToPause(); + try { + Suspension.suspend(remote); + StackTrace.StackFrameData frame = findStackFrame(remote); + safepoint.invoke(remote, target, pause.slot, frame.depth); + } finally { + Suspension.resume(remote); + pause.wakeupOtherThread(); + remote.join(); + } + } + + private StackTrace.StackFrameData findStackFrame(Thread thr) { + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) { + if (frame.method.equals(target)) { + return frame; + } + } + throw new Error("Unable to find stack frame in method " + target + " on thread " + thr); + } + } + public static Method getMethod(String name) throws Exception { + return Class.forName("art_test.TestCases1966").getDeclaredMethod(name, IntConsumer.class); + } + + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + final TestCase[] MAIN_TEST_CASES = new TestCase[] { + new TestCase(getMethod("ObjectMethod")), + new TestCase(getMethod("CastInterfaceMethod")), + new TestCase(getMethod("CastExactMethod")), + new TestCase(getMethod("InterfaceMethod")), + new TestCase(getMethod("ExactClassMethod")), + new TestCase(getMethod("PrimitiveMethod")), + new TestCase(getMethod("NullMethod")), + new TestCase(getMethod("CastExactNullMethod")), + new TestCase(getMethod("CastInterfaceNullMethod")), + }; + + final SetterFunction set_obj = Locals::SetLocalVariableObject; + final SafepointFunction[] SAFEPOINTS = new SafepointFunction[] { + NamedGet("GetObject", Locals::GetLocalVariableObject), + NamedSet("Null", set_obj, null), + NamedSet("TestClass1", set_obj, new TestClass1("Set TestClass1")), + NamedSet("TestClass1ext", set_obj, new TestClass1ext("Set TestClass1ext")), + NamedSet("TestClass2", set_obj, new TestClass2("Set TestClass2")), + NamedSet("TestClass2impl", set_obj, new TestClass2impl("Set TestClass2impl")), + }; + + for (TestCase t : MAIN_TEST_CASES) { + for (SafepointFunction s : SAFEPOINTS) { + t.exec(s); + } + } + } +} |