• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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