1 /* 2 * Copyright (C) 2024 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 package android.os.instrumentation.cts; 17 18 import static android.Manifest.permission.DYNAMIC_INSTRUMENTATION; 19 20 import static com.android.art.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS; 21 22 import static com.google.common.truth.Truth.assertThat; 23 24 import android.os.Process; 25 import android.platform.test.annotations.RequiresFlagsEnabled; 26 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 27 28 import androidx.annotation.NonNull; 29 import androidx.annotation.Nullable; 30 31 import com.android.bedstead.harrier.BedsteadJUnit4; 32 import com.android.bedstead.harrier.DeviceState; 33 import com.android.bedstead.permissions.annotations.EnsureDoesNotHavePermission; 34 import com.android.bedstead.permissions.annotations.EnsureHasPermission; 35 36 import org.junit.ClassRule; 37 import org.junit.Rule; 38 import org.junit.Test; 39 import org.junit.rules.RuleChain; 40 import org.junit.rules.TestRule; 41 import org.junit.runner.RunWith; 42 43 @RunWith(BedsteadJUnit4.class) 44 public class DynamicInstrumentationManagerTest { 45 private static final String SYSTEM_SERVER = "system_server"; 46 private static final String FQCN_IN_ART_PROFILE = 47 "com.android.server.am.ActivityManagerService$LocalService"; 48 private static final String METHOD_IN_ART_PROFILE = "checkContentProviderAccess"; 49 private static final String[] PARAMS_IN_ART_PROFILE = new String[]{"java.lang.String", "int"}; 50 private static final String FQCN_NOT_IN_ART_PROFILE = 51 "com.android.server.os.instrumentation" 52 + ".DynamicInstrumentationManagerService$BinderService"; 53 private static final String METHOD_NOT_IN_ART_PROFILE = "getExecutableMethodFileOffsets"; 54 private static final String[] PARAMS_NOT_IN_ART_PROFILE = new String[]{ 55 "android.os.instrumentation.TargetProcess", 56 "android.os.instrumentation.MethodDescriptor", 57 "android.os.instrumentation.IOffsetCallback"}; 58 59 static { 60 System.loadLibrary("dynamic_instrumentation_manager_test_jni"); 61 } 62 63 @ClassRule 64 public static final DeviceState sDeviceState = new DeviceState(); 65 66 @Rule 67 public final TestRule chain = RuleChain.outerRule( 68 DeviceFlagsValueProvider.createCheckFlagsRule()).around(sDeviceState); 69 70 @Test 71 @RequiresFlagsEnabled({ 72 FLAG_EXECUTABLE_METHOD_FILE_OFFSETS, 73 android.uprobestats.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS 74 }) 75 @EnsureHasPermission(DYNAMIC_INSTRUMENTATION) aotCompiled()76 public void aotCompiled() { 77 OffsetsWithStatusCode result = getOffsetsWithStatusCode(FQCN_IN_ART_PROFILE, 78 METHOD_IN_ART_PROFILE, 79 PARAMS_IN_ART_PROFILE); 80 assertThat(result.statusCode).isEqualTo(0); 81 assertThat(result.offsets).isNotNull(); 82 // Normally, the container path is a path ending in services.odex, but 83 // it is occasionally some other cache file e.g. ...@services.jar@classes.odex. 84 assertThat(result.offsets.containerPath).contains("services"); 85 assertThat(result.offsets.containerPath).endsWith(".odex"); 86 assertThat(result.offsets.containerOffset).isGreaterThan(0); 87 assertThat(result.offsets.methodOffset).isGreaterThan(0); 88 } 89 90 @Test 91 @RequiresFlagsEnabled({ 92 FLAG_EXECUTABLE_METHOD_FILE_OFFSETS, 93 android.uprobestats.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS 94 }) 95 @EnsureHasPermission(DYNAMIC_INSTRUMENTATION) appAotCompiled()96 public void appAotCompiled() throws Exception { 97 OffsetsWithStatusCode result = getOffsetsWithStatusCode( 98 Process.myUid(), Process.myPid(), 99 Process.myProcessName(), 100 "android.os.SystemClock", 101 "elapsedRealtime", new String[0]); 102 assertThat(result.statusCode).isEqualTo(0); 103 assertThat(result.offsets).isNotNull(); 104 assertThat(result.offsets.containerPath).isNotEmpty(); 105 assertThat(result.offsets.containerOffset).isGreaterThan(0); 106 assertThat(result.offsets.methodOffset).isGreaterThan(0); 107 } 108 109 @Test 110 @RequiresFlagsEnabled(FLAG_EXECUTABLE_METHOD_FILE_OFFSETS) 111 @EnsureHasPermission(DYNAMIC_INSTRUMENTATION) jitCompiled_null()112 public void jitCompiled_null() { 113 OffsetsWithStatusCode result = getOffsetsWithStatusCode( 114 FQCN_NOT_IN_ART_PROFILE, METHOD_NOT_IN_ART_PROFILE, PARAMS_NOT_IN_ART_PROFILE); 115 assertThat(result.statusCode).isEqualTo(0); 116 assertThat(result.offsets).isNull(); 117 } 118 119 @Test 120 @RequiresFlagsEnabled({ 121 FLAG_EXECUTABLE_METHOD_FILE_OFFSETS, 122 android.uprobestats.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS 123 }) 124 @EnsureDoesNotHavePermission(DYNAMIC_INSTRUMENTATION) noPermission_SecurityException()125 public void noPermission_SecurityException() { 126 OffsetsWithStatusCode result = getOffsetsWithStatusCode(FQCN_IN_ART_PROFILE, 127 METHOD_IN_ART_PROFILE, 128 PARAMS_IN_ART_PROFILE); 129 assertThat(result.statusCode).isEqualTo(-1); 130 assertThat(result.offsets).isNull(); 131 } 132 133 @Test 134 @RequiresFlagsEnabled({ 135 FLAG_EXECUTABLE_METHOD_FILE_OFFSETS, 136 android.uprobestats.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS 137 }) 138 @EnsureHasPermission(DYNAMIC_INSTRUMENTATION) appProcessNotFound()139 public void appProcessNotFound() throws Exception { 140 OffsetsWithStatusCode result = getOffsetsWithStatusCode(0, 0, "foo", FQCN_IN_ART_PROFILE, 141 METHOD_IN_ART_PROFILE, PARAMS_IN_ART_PROFILE); 142 assertThat(result.offsets).isNull(); 143 } 144 145 @Test 146 @RequiresFlagsEnabled({ 147 FLAG_EXECUTABLE_METHOD_FILE_OFFSETS, 148 android.uprobestats.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS 149 }) 150 @EnsureHasPermission(DYNAMIC_INSTRUMENTATION) notFound_IllegalArgumentException()151 public void notFound_IllegalArgumentException() { 152 OffsetsWithStatusCode result = getOffsetsWithStatusCode("", "", new String[]{}); 153 assertThat(result.statusCode).isEqualTo(-3); 154 assertThat(result.offsets).isNull(); 155 } 156 getOffsetsWithStatusCode(String fqcn, String methodName, String[] fqParameters)157 private static OffsetsWithStatusCode getOffsetsWithStatusCode(String fqcn, String methodName, 158 String[] fqParameters) { 159 return getOffsetsWithStatusCode(0, 0, SYSTEM_SERVER, fqcn, methodName, 160 fqParameters); 161 } 162 getOffsetsWithStatusCode( int uid, int pid, String processName, String fqcn, String methodName, String[] fqParameters)163 private static OffsetsWithStatusCode getOffsetsWithStatusCode( 164 int uid, int pid, String processName, String fqcn, String methodName, 165 String[] fqParameters) { 166 return getExecutableMethodFileOffsetsNative(uid, pid, processName, fqcn, methodName, 167 fqParameters); 168 } 169 170 private static class OffsetsWithStatusCode { 171 public final int statusCode; 172 public final @Nullable ExecutableMethodFileOffsets offsets; 173 OffsetsWithStatusCode( int statusCode, @Nullable ExecutableMethodFileOffsets offsets)174 private OffsetsWithStatusCode( 175 int statusCode, @Nullable ExecutableMethodFileOffsets offsets) { 176 this.statusCode = statusCode; 177 this.offsets = offsets; 178 } 179 } 180 181 private static class ExecutableMethodFileOffsets { 182 public final @NonNull String containerPath; 183 public final long containerOffset; 184 public final long methodOffset; 185 ExecutableMethodFileOffsets(@onNull String containerPath, long containerOffset, long methodOffset)186 private ExecutableMethodFileOffsets(@NonNull String containerPath, long containerOffset, 187 long methodOffset) { 188 this.containerPath = containerPath; 189 this.containerOffset = containerOffset; 190 this.methodOffset = methodOffset; 191 } 192 } 193 getExecutableMethodFileOffsetsNative( int uid, int pid, String processName, String fqcn, String methodName, String[] fqParameters)194 private static native OffsetsWithStatusCode getExecutableMethodFileOffsetsNative( 195 int uid, int pid, String processName, String fqcn, String methodName, 196 String[] fqParameters); 197 } 198