1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package android.jvmti.cts; 16 17 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 18 import com.android.ddmlib.NullOutputReceiver; 19 import com.android.tradefed.build.IBuildInfo; 20 import com.android.tradefed.config.Option; 21 import com.android.tradefed.device.ITestDevice; 22 import com.android.tradefed.log.LogUtil.CLog; 23 import com.android.tradefed.testtype.DeviceTestCase; 24 import com.android.tradefed.testtype.IAbi; 25 import com.android.tradefed.testtype.IAbiReceiver; 26 import com.android.tradefed.testtype.IBuildReceiver; 27 import com.android.tradefed.util.AbiUtils; 28 import com.android.tradefed.util.ArrayUtil; 29 import com.android.tradefed.util.FileUtil; 30 import com.android.tradefed.util.IRunUtil; 31 import com.android.tradefed.util.RunUtil; 32 import com.android.tradefed.util.ZipUtil; 33 34 import java.io.DataInputStream; 35 import java.io.File; 36 import java.net.ServerSocket; 37 import java.net.Socket; 38 import java.util.concurrent.TimeUnit; 39 import java.util.zip.ZipFile; 40 41 /** 42 * Specialization of JvmtiHostTest to test attaching on startup. 43 */ 44 public class JvmtiAttachingHostTest extends DeviceTestCase implements IBuildReceiver, IAbiReceiver { 45 // inject these options from HostTest directly using --set-option <option name>:<option value> 46 @Option(name = "package-name", 47 description = "The package name of the device test", 48 mandatory = true) 49 private String mTestPackageName = null; 50 51 @Option(name = "test-file-name", 52 description = "the name of a test zip file to install on device.", 53 mandatory = true) 54 private String mTestApk = null; 55 56 private CompatibilityBuildHelper mBuildHelper; 57 private IAbi mAbi; 58 private int mCurrentUser; 59 60 @Override setBuild(IBuildInfo arg0)61 public void setBuild(IBuildInfo arg0) { 62 mBuildHelper = new CompatibilityBuildHelper(arg0); 63 } 64 65 @Override setAbi(IAbi arg0)66 public void setAbi(IAbi arg0) { 67 mAbi = arg0; 68 } 69 70 private static interface TestRun { run(ITestDevice device, String pkg, String apk, String abiName)71 public void run(ITestDevice device, String pkg, String apk, String abiName); 72 } 73 74 private final static String AGENT = "libctsjvmtiattachagent.so"; 75 76 private final static String STARTUP_AGENT_DIR = "code_cache/startup_agents"; 77 78 private static String REMOTE_SOCKET_NAME = "CtsJvmtiAttachingHostTestCases_SOCKET"; 79 80 @Override setUp()81 protected void setUp() throws Exception { 82 mCurrentUser = getDevice().getCurrentUser(); 83 } 84 testJvmtiAttachDuringBind()85 public void testJvmtiAttachDuringBind() throws Exception { 86 runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> { 87 try { 88 runAttachTestCmd(device, pkg, "--attach-agent-bind " + AGENT); 89 } catch (Exception e) { 90 throw new RuntimeException("Failed bind-time attaching", e); 91 } 92 }); 93 } 94 testJvmtiAttachEarly()95 public void testJvmtiAttachEarly() throws Exception { 96 runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> { 97 try { 98 String pwd = device.executeShellCommand( 99 "run-as " + pkg + " --user " + mCurrentUser + " pwd"); 100 if (pwd == null) { 101 throw new RuntimeException("pwd failed"); 102 } 103 pwd = pwd.trim(); 104 if (pwd.isEmpty()) { 105 throw new RuntimeException("pwd failed"); 106 } 107 108 // Give it a different name, so we do not have "contamination" from 109 // the test APK. 110 String libInDataData = AGENT.substring(0, AGENT.length() - ".so".length()) 111 + "2.so"; 112 String agentInDataData = 113 installLibToDataData(device, pkg, abiName, apk, pwd, AGENT, 114 libInDataData); 115 runAttachTestCmd(device, pkg, "--attach-agent " + agentInDataData); 116 } catch (Exception e) { 117 throw new RuntimeException("Failed pre-bind attaching", e); 118 } 119 }); 120 } 121 testJvmtiAgentStartupAgents()122 public void testJvmtiAgentStartupAgents() throws Exception { 123 runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> { 124 String startup_dir = null; 125 try { 126 startup_dir = getPwd(device, pkg) + "/" + STARTUP_AGENT_DIR; 127 device.executeShellCommand( 128 "run-as " + pkg + " --user " + mCurrentUser + " mkdir -p " + startup_dir); 129 } catch (Exception e) { 130 throw new RuntimeException("Failed to install startup-agents"); 131 } 132 try { 133 installLibToDataData(device, pkg, abiName, apk, startup_dir, AGENT, AGENT); 134 // Run and check attach occurs. 135 runAttachTestCmd(device, pkg, ""); 136 runAttachTestCmd(device, pkg, ""); 137 } catch (Exception e) { 138 throw new RuntimeException("Failed startup_agents attaching", e); 139 } finally { 140 try { 141 // Cleanup the startup-agents directory 142 device.executeShellCommand( 143 "run-as " + pkg + " --user " + mCurrentUser + " rm -rf " + startup_dir); 144 } catch (Exception e) { 145 throw new RuntimeException("Failed to clean up " + startup_dir, e); 146 } 147 } 148 }); 149 } 150 testJvmtiAgentAppInternal()151 public void testJvmtiAgentAppInternal() throws Exception { 152 runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> { 153 try { 154 String setAgentAppCmd = "cmd activity set-agent-app " + pkg + " " + AGENT; 155 device.executeShellCommand(setAgentAppCmd); 156 } catch (Exception e) { 157 throw new RuntimeException("Failed running set-agent-app", e); 158 } 159 160 try { 161 runAttachTestCmd(device, pkg, ""); 162 163 // And again. 164 runAttachTestCmd(device, pkg, ""); 165 } catch (Exception e) { 166 throw new RuntimeException("Failed agent-app attaching", e); 167 } 168 }); 169 } 170 testJvmtiAgentAppExternal()171 public void testJvmtiAgentAppExternal() throws Exception { 172 runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> { 173 try { 174 String pwd = getPwd(device, pkg); 175 // Give it a different name, so we do not have "contamination" from 176 // the test APK. 177 String libInDataData = AGENT.substring(0, AGENT.length() - ".so".length()) 178 + "2.so"; 179 String agentInDataData = 180 installLibToDataData(device, pkg, abiName, apk, pwd, AGENT, 181 libInDataData); 182 183 String setAgentAppCmd = "cmd activity set-agent-app " + pkg + " " + agentInDataData; 184 device.executeShellCommand(setAgentAppCmd); 185 } catch (Exception e) { 186 throw new RuntimeException("Failed running set-agent-app", e); 187 } 188 189 try { 190 runAttachTestCmd(device, pkg, ""); 191 192 // And again. 193 runAttachTestCmd(device, pkg, ""); 194 } catch (Exception e) { 195 throw new RuntimeException("Failed agent-app attaching", e); 196 } 197 }); 198 } 199 getPwd(ITestDevice device, String pkg)200 private String getPwd(ITestDevice device, String pkg) throws Exception { 201 String pwd = device.executeShellCommand( 202 "run-as " + pkg + " --user " + mCurrentUser + " pwd"); 203 if (pwd == null) { 204 throw new RuntimeException("pwd failed"); 205 } 206 pwd = pwd.trim(); 207 if (pwd.isEmpty()) { 208 throw new RuntimeException("pwd failed"); 209 } 210 return pwd; 211 } 212 runJvmtiAgentLoadTest(TestRun runner)213 private void runJvmtiAgentLoadTest(TestRun runner) throws Exception { 214 final ITestDevice device = getDevice(); 215 216 String testingArch = AbiUtils.getBaseArchForAbi(mAbi.getName()); 217 String deviceArch = getDeviceBaseArch(device); 218 219 //Only bypass if Base Archs are different 220 if (!testingArch.equals(deviceArch)) { 221 CLog.d( 222 "Bypass as testing Base Arch:" 223 + testingArch 224 + " is different from DUT Base Arch:" 225 + deviceArch); 226 return; 227 } 228 229 if (mTestApk == null || mTestPackageName == null) { 230 throw new IllegalStateException("Incorrect configuration"); 231 } 232 233 // Wakeup the device if it is on the lockscreen and move it to the home screen. 234 device.executeShellCommand("input keyevent KEYCODE_WAKEUP"); 235 device.executeShellCommand("wm dismiss-keyguard"); 236 device.executeShellCommand("input keyevent KEYCODE_HOME"); 237 238 runner.run(device, mTestPackageName, mTestApk, mAbi.getName()); 239 } 240 getDeviceBaseArch(ITestDevice device)241 private String getDeviceBaseArch(ITestDevice device) throws Exception { 242 String abi = device.executeShellCommand("getprop ro.product.cpu.abi").replace("\n", ""); 243 CLog.d("DUT abi:" + abi); 244 return AbiUtils.getBaseArchForAbi(abi); 245 } 246 runAttachTestCmd(ITestDevice device, String pkg, String agentParams)247 private static void runAttachTestCmd(ITestDevice device, String pkg, String agentParams) 248 throws Exception { 249 // Get a reverse socket setup 250 try (final ServerSocket ss = new ServerSocket(0)) { 251 device.executeAdbCommand( 252 "reverse", "localabstract:" + REMOTE_SOCKET_NAME, "tcp:" + ss.getLocalPort()); 253 String attachCmd = "cmd activity start -S " + agentParams + " -n " + pkg 254 + "/android.jvmti.JvmtiActivity"; 255 256 // Don't try to parse the output. We'll get data from the socket or a timeout if it 257 // didn't start. For some reason this command sometimes fails. Retry up to ten times to 258 // make the test less flaky. 259 device.executeShellCommand(attachCmd, NullOutputReceiver.getReceiver(), 10, 260 TimeUnit.SECONDS, 10); 261 // Wait for startup up to 30 seconds. 262 ss.setSoTimeout(30000); 263 try (Socket s = ss.accept()) { 264 DataInputStream dis = new DataInputStream(s.getInputStream()); 265 String res = dis.readUTF(); 266 s.shutdownInput(); 267 if (!res.trim().equals("SUCCESS")) { 268 throw new RuntimeException("Failed test due to remote error: " + res); 269 } 270 } catch (Exception e) { 271 throw new RuntimeException("Failed to read output", e); 272 } 273 } finally { 274 device.executeAdbCommand("reverse", "--remove", "localabstract:" + REMOTE_SOCKET_NAME); 275 } 276 } 277 installLibToDataData(ITestDevice device, String pkg, String abiName, String apk, String dataData, String library, String newLibName)278 private String installLibToDataData(ITestDevice device, String pkg, String abiName, 279 String apk, String dataData, String library, String newLibName) throws Exception { 280 ZipFile zf = null; 281 File tmpFile = null; 282 String libInTmp = null; 283 try { 284 String libInDataData = dataData + "/" + newLibName; 285 286 File apkFile = mBuildHelper.getTestFile(apk); 287 zf = new ZipFile(apkFile); 288 289 String libPathInApk = "lib/" + abiName + "/" + library; 290 tmpFile = ZipUtil.extractFileFromZip(zf, libPathInApk); 291 292 libInTmp = "/data/local/tmp/" + tmpFile.getName(); 293 if (!device.pushFile(tmpFile, libInTmp)) { 294 throw new RuntimeException("Could not push library " + library + " to device"); 295 } 296 297 String runAsCp = device.executeShellCommand( 298 "run-as " + pkg + " --user " + mCurrentUser + 299 " cp " + libInTmp + " " + libInDataData); 300 if (runAsCp != null && !runAsCp.trim().isEmpty()) { 301 throw new RuntimeException(runAsCp.trim()); 302 } 303 304 String runAsChmod = device.executeShellCommand( 305 "run-as " + pkg + " --user " + mCurrentUser + " chmod a+x " + libInDataData); 306 if (runAsChmod != null && !runAsChmod.trim().isEmpty()) { 307 throw new RuntimeException(runAsChmod.trim()); 308 } 309 310 return libInDataData; 311 } finally { 312 FileUtil.deleteFile(tmpFile); 313 ZipUtil.closeZip(zf); 314 if (libInTmp != null) { 315 try { 316 device.executeShellCommand("rm " + libInTmp); 317 } catch (Exception e) { 318 CLog.e("Failed cleaning up library on device"); 319 } 320 } 321 } 322 } 323 } 324