| // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.debug; |
| |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.code.IfEqz; |
| import com.android.tools.r8.code.Instruction; |
| import com.android.tools.r8.debuginfo.DebugInfoInspector; |
| import com.android.tools.r8.graph.DexDebugEntry; |
| import com.android.tools.r8.naming.MemberNaming.MethodSignature; |
| import com.android.tools.r8.smali.SmaliBuilder; |
| import com.android.tools.r8.utils.AndroidApp; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.Collections; |
| import java.util.List; |
| import org.apache.harmony.jpda.tests.framework.jdwp.Value; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TemporaryFolder; |
| |
| public class SmaliDebugTest extends DebugTestBase { |
| |
| static final String FILE = "SmaliDebugTestDebuggee.smali"; |
| static final String CLASS = "SmaliDebugTestDebuggee"; |
| |
| @Rule |
| public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); |
| |
| /** |
| * Simple test to check setup works for the Java source, ala: |
| * |
| * static int method(int x) { |
| * if (x != 0) { |
| * return x |
| * } else { |
| * return 42; |
| * } |
| * } |
| */ |
| @Test |
| public void testSimpleIf() throws Throwable { |
| String methodName = "simpleIf"; |
| runDebugTest( |
| new DexDebugTestConfig(buildSimpleIf(methodName)), |
| CLASS, |
| breakpoint(CLASS, methodName), |
| run(), |
| // first call x == 0 |
| checkLine(FILE, 1), |
| checkLocal("x", Value.createInt(0)), |
| stepOver(), |
| checkLine(FILE, 4), |
| run(), |
| // second call x == 1 |
| checkLine(FILE, 1), |
| checkLocal("x", Value.createInt(1)), |
| stepOver(), |
| checkLine(FILE, 2), |
| run()); |
| } |
| |
| private List<Path> buildSimpleIf(String methodName) |
| throws Throwable { |
| SmaliBuilder builder = new SmaliBuilder(); |
| builder.addClass(CLASS); |
| builder.setSourceFile(FILE); |
| builder.addStaticMethod("int", methodName, Collections.singletonList("int"), 0, |
| ".param p0, \"x\" # I", |
| ".line 1", |
| " if-eqz p0, :onzero", |
| ".line 2", |
| " return p0", |
| ":onzero", |
| ".line 4", |
| " const p0, 42", |
| " return p0"); |
| |
| builder.addMainMethod(2, |
| " const v0, 0", |
| " invoke-static { v0 }, L" + CLASS + ";->" + methodName + "(I)I", |
| " move-result v1", |
| " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", |
| " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V", |
| " const v0, 1", |
| " invoke-static { v0 }, L" + CLASS + ";->" + methodName + "(I)I", |
| " move-result v1", |
| " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", |
| " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V", |
| " return-void" |
| ); |
| return buildAndRun(builder); |
| } |
| |
| /** |
| * Test that a jump to a position that happens after a "default" debug event still triggers a |
| * break point due to the line change. |
| */ |
| @Test |
| public void testJumpAfterLineChange() throws Throwable { |
| String methodName = "jumpAfterLineChange"; |
| List<Path> outs = buildJumpAfterLineChange(methodName); |
| |
| // Verify that the PC associated with the line entry 4 is prior to the target of the condition. |
| DebugInfoInspector info = |
| new DebugInfoInspector( |
| AndroidApp.builder().addProgramFiles(outs).build(), |
| CLASS, |
| new MethodSignature(methodName, "int", new String[] {"int"})); |
| IfEqz cond = null; |
| for (Instruction instruction : info.getMethod().getCode().asDexCode().instructions) { |
| if (instruction.getOpcode() == IfEqz.OPCODE) { |
| cond = (IfEqz) instruction; |
| break; |
| } |
| } |
| assertNotNull(cond); |
| int target = cond.getTargets()[0] + cond.getOffset(); |
| int linePC = -1; |
| for (DexDebugEntry entry : info.getEntries()) { |
| if (entry.line == 4) { |
| linePC = entry.address; |
| break; |
| } |
| } |
| assertTrue(linePC > -1); |
| assertTrue(target > linePC); |
| |
| // Run debugger to verify that we step to line 4 and the values of v0 and v1 are unchanged. |
| runDebugTest( |
| new DexDebugTestConfig(outs), |
| CLASS, |
| breakpoint(CLASS, methodName), |
| run(), |
| // first call x == 0 |
| checkLine(FILE, 1), |
| checkLocal("x", Value.createInt(0)), |
| checkNoLocal("v0"), |
| checkNoLocal("v1"), |
| stepOver(), |
| checkLine(FILE, 4), |
| checkLocal("v0", Value.createInt(0)), |
| checkLocal("v1", Value.createInt(7)), |
| stepOver(), |
| checkLine(FILE, 5), |
| checkLocal("v0", Value.createInt(42)), |
| checkLocal("v1", Value.createInt(7)), |
| run(), |
| // second call x == 1 |
| checkLine(FILE, 1), |
| checkLocal("x", Value.createInt(1)), |
| checkNoLocal("v0"), |
| checkNoLocal("v1"), |
| stepOver(), |
| checkLine(FILE, 2), |
| checkNoLocal("v0"), |
| checkNoLocal("v1"), |
| run()); |
| } |
| |
| private List<Path> buildJumpAfterLineChange(String methodName) |
| throws Throwable { |
| SmaliBuilder builder = new SmaliBuilder(); |
| builder.addClass(CLASS); |
| builder.setSourceFile(FILE); |
| builder.addStaticMethod("int", methodName, Collections.singletonList("int"), 2, |
| ".param p0, \"x\" # I", |
| ".line 1", |
| " const v0, 0", |
| " const v1, 7", |
| " if-eqz p0, :onzero", |
| ".line 2", |
| " return p0", |
| ".line 4", |
| ".local v0, \"v0\":I", |
| ".local v1, \"v1\":I", |
| " const v0, 1234", // odd and unreachable code |
| " const v1, 5678", // ... |
| ":onzero", |
| " const v0, 42", |
| ".line 5", |
| " return v0"); |
| |
| builder.addMainMethod(2, |
| " const v0, 0", |
| " invoke-static { v0 }, L" + CLASS + ";->" + methodName + "(I)I", |
| " move-result v1", |
| " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", |
| " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V", |
| " const v0, 1", |
| " invoke-static { v0 }, L" + CLASS + ";->" + methodName + "(I)I", |
| " move-result v1", |
| " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", |
| " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V", |
| " return-void" |
| ); |
| return buildAndRun(builder); |
| } |
| |
| private List<Path> buildAndRun(SmaliBuilder builder) throws Throwable { |
| byte[] bytes = builder.compile(); |
| Path out = temp.getRoot().toPath().resolve("classes.dex"); |
| Files.write(out, bytes); |
| ToolHelper.runArtNoVerificationErrors(out.toString(), CLASS); |
| return Collections.singletonList(out); |
| } |
| } |