diff options
28 files changed, 996 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp index 26d0d65f329c..2024c8afb5be 100644 --- a/Android.bp +++ b/Android.bp @@ -399,6 +399,7 @@ java_defaults { "com.android.sysprop.foldlockbehavior", "com.android.sysprop.view", "framework-internal-utils", + "dynamic_instrumentation_manager_aidl-java", // If MimeMap ever becomes its own APEX, then this dependency would need to be removed // in favor of an API stubs dependency in java_library "framework" below. "mimemap", diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 2a01ca082832..2535d2dda003 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -138,6 +138,7 @@ package android { field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS"; field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE"; field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT"; + field @FlaggedApi("com.android.art.flags.executable_method_file_offsets") public static final String DYNAMIC_INSTRUMENTATION = "android.permission.DYNAMIC_INSTRUMENTATION"; field @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission") public static final String EMBED_ANY_APP_IN_UNTRUSTED_MODE = "android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE"; field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES"; field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED"; diff --git a/core/java/Android.bp b/core/java/Android.bp index 9875efe04361..81ed11ae9af0 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -28,6 +28,7 @@ filegroup { exclude_srcs: [ "android/os/*MessageQueue/**/*.java", "android/ranging/**/*.java", + ":dynamic_instrumentation_manager_aidl_sources", ], visibility: ["//frameworks/base"], } @@ -120,6 +121,17 @@ filegroup { } filegroup { + name: "dynamic_instrumentation_manager_aidl_sources", + srcs: ["android/os/instrumentation/*.aidl"], +} + +aidl_interface { + name: "dynamic_instrumentation_manager_aidl", + srcs: [":dynamic_instrumentation_manager_aidl_sources"], + unstable: true, +} + +filegroup { name: "framework-internal-display-sources", srcs: ["com/android/internal/display/BrightnessSynchronizer.java"], visibility: ["//frameworks/base/services/tests/mockingservicestests"], diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 186f7b3e111c..6086f2455a31 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -6802,6 +6802,12 @@ public abstract class Context { public static final String MEDIA_QUALITY_SERVICE = "media_quality"; /** + * Service to perform operations needed for dynamic instrumentation. + * @hide + */ + public static final String DYNAMIC_INSTRUMENTATION_SERVICE = "dynamic_instrumentation"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl b/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl new file mode 100644 index 000000000000..dbe54891b0f2 --- /dev/null +++ b/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 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 android.os.instrumentation; + +/** + * Represents the location of the code for a compiled method within a process' + * memory. + * {@hide} + */ +@JavaDerive(toString=true) +parcelable ExecutableMethodFileOffsets { + /** + * The OS path of the containing file (could be virtual). + */ + @utf8InCpp String containerPath; + /** + * The offset of the containing file within the process' memory. + */ + long containerOffset; + /** + * The offset of the method within the containing file. + */ + long methodOffset; +} diff --git a/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl new file mode 100644 index 000000000000..c45c51d15cc9 --- /dev/null +++ b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 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 android.os.instrumentation; + +import android.os.instrumentation.ExecutableMethodFileOffsets; +import android.os.instrumentation.MethodDescriptor; +import android.os.instrumentation.TargetProcess; + +/** + * System private API for managing the dynamic attachment of instrumentation. + * + * {@hide} + */ +interface IDynamicInstrumentationManager { + /** Provides ART metadata about the described compiled method within the target process */ + @PermissionManuallyEnforced + @nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets( + in TargetProcess targetProcess, in MethodDescriptor methodDescriptor); +} diff --git a/core/java/android/os/instrumentation/MethodDescriptor.aidl b/core/java/android/os/instrumentation/MethodDescriptor.aidl new file mode 100644 index 000000000000..055d0ecb66e4 --- /dev/null +++ b/core/java/android/os/instrumentation/MethodDescriptor.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 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 android.os.instrumentation; + +/** + * Represents a JVM method, where class fields that make up its signature. + * {@hide} + */ +@JavaDerive(toString=true) +parcelable MethodDescriptor { + /** + * Fully qualified class in reverse.domain.Naming + */ + @utf8InCpp String fullyQualifiedClassName; + /** + * Name of the method. + */ + @utf8InCpp String methodName; + /** + * Fully qualified types of method parameters, or string representations if primitive e.g. "int". + */ + @utf8InCpp String[] fullyQualifiedParameters; +} diff --git a/core/java/android/os/instrumentation/TargetProcess.aidl b/core/java/android/os/instrumentation/TargetProcess.aidl new file mode 100644 index 000000000000..e90780d07ef2 --- /dev/null +++ b/core/java/android/os/instrumentation/TargetProcess.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 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 android.os.instrumentation; + +/** + * Addresses a process that would run on the device. + * Helps disambiguate targeted processes in cases of pid re-use. + * {@hide} + */ +@JavaDerive(toString=true) +parcelable TargetProcess { + int uid; + int pid; + @utf8InCpp String processName; +} diff --git a/core/res/Android.bp b/core/res/Android.bp index 66c2e12f7cdf..cdf88f7ee33c 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -171,6 +171,7 @@ android_app { "android.security.flags-aconfig", "com.android.hardware.input.input-aconfig", "aconfig_trade_in_mode_flags", + "art-aconfig-flags", "ranging_aconfig_flags", ], } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5913992004b8..a79ad4aa4b89 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8482,6 +8482,16 @@ android:protectionLevel="signature|privileged" android:featureFlag="com.android.tradeinmode.flags.enable_trade_in_mode" /> + <!-- @SystemApi + @FlaggedApi(com.android.art.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS) + Ability to read program metadata and attach dynamic instrumentation. + <p>Protection level: signature + @hide + --> + <permission android:name="android.permission.DYNAMIC_INSTRUMENTATION" + android:protectionLevel="signature" + android:featureFlag="com.android.art.flags.executable_method_file_offsets" /> + <!-- @TestApi Signature permission reserved for testing. This should never be used to diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 7b96699f7f71..857df1038df1 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -213,6 +213,8 @@ <assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="gpu_service" /> <assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="keystore" /> + <assign-permission name="android.permission.DYNAMIC_INSTRUMENTATION" uid="uprobestats" /> + <split-permission name="android.permission.ACCESS_FINE_LOCATION"> <new-permission name="android.permission.ACCESS_COARSE_LOCATION" /> </split-permission> diff --git a/native/android/Android.bp b/native/android/Android.bp index 3eb99c3387f7..da29c49f9d7b 100644 --- a/native/android/Android.bp +++ b/native/android/Android.bp @@ -55,6 +55,7 @@ cc_library_shared { "surface_control_input_receiver.cpp", "choreographer.cpp", "configuration.cpp", + "dynamic_instrumentation_manager.cpp", "hardware_buffer_jni.cpp", "input.cpp", "input_transfer_token.cpp", @@ -100,6 +101,7 @@ cc_library_shared { "android.hardware.configstore@1.0", "android.hardware.configstore-utils", "android.os.flags-aconfig-cc", + "dynamic_instrumentation_manager_aidl-cpp", "libnativedisplay", "libfmq", ], diff --git a/native/android/dynamic_instrumentation_manager.cpp b/native/android/dynamic_instrumentation_manager.cpp new file mode 100644 index 000000000000..d9bacb116f96 --- /dev/null +++ b/native/android/dynamic_instrumentation_manager.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2024 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. + */ + +#define LOG_TAG "ADynamicInstrumentationManager" +#include <android/dynamic_instrumentation_manager.h> +#include <android/os/instrumentation/ExecutableMethodFileOffsets.h> +#include <android/os/instrumentation/IDynamicInstrumentationManager.h> +#include <android/os/instrumentation/MethodDescriptor.h> +#include <android/os/instrumentation/TargetProcess.h> +#include <binder/Binder.h> +#include <binder/IServiceManager.h> +#include <utils/Log.h> + +#include <mutex> +#include <optional> +#include <string> +#include <vector> + +namespace android::dynamicinstrumentationmanager { + +// Global instance of IDynamicInstrumentationManager, service is obtained only on first use. +static std::mutex mLock; +static sp<os::instrumentation::IDynamicInstrumentationManager> mService; + +sp<os::instrumentation::IDynamicInstrumentationManager> getService() { + std::lock_guard<std::mutex> scoped_lock(mLock); + if (mService == nullptr || !IInterface::asBinder(mService)->isBinderAlive()) { + sp<IBinder> binder = + defaultServiceManager()->waitForService(String16("dynamic_instrumentation")); + mService = interface_cast<os::instrumentation::IDynamicInstrumentationManager>(binder); + } + return mService; +} + +} // namespace android::dynamicinstrumentationmanager + +using namespace android; +using namespace dynamicinstrumentationmanager; + +struct ADynamicInstrumentationManager_TargetProcess { + uid_t uid; + uid_t pid; + std::string processName; + + ADynamicInstrumentationManager_TargetProcess(uid_t uid, pid_t pid, const char* processName) + : uid(uid), pid(pid), processName(processName) {} +}; + +ADynamicInstrumentationManager_TargetProcess* ADynamicInstrumentationManager_TargetProcess_create( + uid_t uid, pid_t pid, const char* processName) { + return new ADynamicInstrumentationManager_TargetProcess(uid, pid, processName); +} + +void ADynamicInstrumentationManager_TargetProcess_destroy( + ADynamicInstrumentationManager_TargetProcess* instance) { + delete instance; +} + +struct ADynamicInstrumentationManager_MethodDescriptor { + std::string fqcn; + std::string methodName; + std::vector<std::string> fqParameters; + + ADynamicInstrumentationManager_MethodDescriptor(const char* fqcn, const char* methodName, + const char* fullyQualifiedParameters[], + size_t numParameters) + : fqcn(fqcn), methodName(methodName) { + std::vector<std::string> fqParameters; + fqParameters.reserve(numParameters); + std::copy_n(fullyQualifiedParameters, numParameters, std::back_inserter(fqParameters)); + this->fqParameters = std::move(fqParameters); + } +}; + +ADynamicInstrumentationManager_MethodDescriptor* +ADynamicInstrumentationManager_MethodDescriptor_create(const char* fullyQualifiedClassName, + const char* methodName, + const char* fullyQualifiedParameters[], + size_t numParameters) { + return new ADynamicInstrumentationManager_MethodDescriptor(fullyQualifiedClassName, methodName, + fullyQualifiedParameters, + numParameters); +} + +void ADynamicInstrumentationManager_MethodDescriptor_destroy( + ADynamicInstrumentationManager_MethodDescriptor* instance) { + delete instance; +} + +struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets { + std::string containerPath; + uint64_t containerOffset; + uint64_t methodOffset; +}; + +ADynamicInstrumentationManager_ExecutableMethodFileOffsets* +ADynamicInstrumentationManager_ExecutableMethodFileOffsets_create() { + return new ADynamicInstrumentationManager_ExecutableMethodFileOffsets(); +} + +const char* ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) { + return instance->containerPath.c_str(); +} + +uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) { + return instance->containerOffset; +} + +uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) { + return instance->methodOffset; +} + +void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) { + delete instance; +} + +int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets( + const ADynamicInstrumentationManager_TargetProcess* targetProcess, + const ADynamicInstrumentationManager_MethodDescriptor* methodDescriptor, + ADynamicInstrumentationManager_ExecutableMethodFileOffsets** out) { + android::os::instrumentation::TargetProcess targetProcessParcel; + targetProcessParcel.uid = targetProcess->uid; + targetProcessParcel.pid = targetProcess->pid; + targetProcessParcel.processName = targetProcess->processName; + + android::os::instrumentation::MethodDescriptor methodDescriptorParcel; + methodDescriptorParcel.fullyQualifiedClassName = methodDescriptor->fqcn; + methodDescriptorParcel.methodName = methodDescriptor->methodName; + methodDescriptorParcel.fullyQualifiedParameters = methodDescriptor->fqParameters; + + sp<os::instrumentation::IDynamicInstrumentationManager> service = getService(); + if (service == nullptr) { + return INVALID_OPERATION; + } + + std::optional<android::os::instrumentation::ExecutableMethodFileOffsets> offsets; + binder_status_t result = + service->getExecutableMethodFileOffsets(targetProcessParcel, methodDescriptorParcel, + &offsets) + .exceptionCode(); + if (result != OK) { + return result; + } + + if (offsets != std::nullopt) { + auto* value = new ADynamicInstrumentationManager_ExecutableMethodFileOffsets(); + value->containerPath = offsets->containerPath; + value->containerOffset = offsets->containerOffset; + value->methodOffset = offsets->methodOffset; + *out = value; + } else { + *out = nullptr; + } + + return result; +}
\ No newline at end of file diff --git a/native/android/include_platform/android/dynamic_instrumentation_manager.h b/native/android/include_platform/android/dynamic_instrumentation_manager.h new file mode 100644 index 000000000000..6c46288954bf --- /dev/null +++ b/native/android/include_platform/android/dynamic_instrumentation_manager.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2024 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. + */ + +#ifndef __ADYNAMICINSTRUMENTATIONMANAGER_H__ +#define __ADYNAMICINSTRUMENTATIONMANAGER_H__ + +#include <sys/cdefs.h> +#include <sys/types.h> + +__BEGIN_DECLS + +struct ADynamicInstrumentationManager_MethodDescriptor; +typedef struct ADynamicInstrumentationManager_MethodDescriptor + ADynamicInstrumentationManager_MethodDescriptor; + +struct ADynamicInstrumentationManager_TargetProcess; +typedef struct ADynamicInstrumentationManager_TargetProcess + ADynamicInstrumentationManager_TargetProcess; + +struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets; +typedef struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets + ADynamicInstrumentationManager_ExecutableMethodFileOffsets; + +/** + * Initializes an ADynamicInstrumentationManager_TargetProcess. Caller must clean up when they are + * done with ADynamicInstrumentationManager_TargetProcess_destroy. + * + * @param uid of targeted process. + * @param pid of targeted process. + * @param processName to disambiguate from corner cases that may arise from pid reuse. + */ +ADynamicInstrumentationManager_TargetProcess* _Nonnull + ADynamicInstrumentationManager_TargetProcess_create( + uid_t uid, pid_t pid, const char* _Nonnull processName) __INTRODUCED_IN(36); +/** + * Clean up an ADynamicInstrumentationManager_TargetProcess. + * + * @param instance returned from ADynamicInstrumentationManager_TargetProcess_create. + */ +void ADynamicInstrumentationManager_TargetProcess_destroy( + ADynamicInstrumentationManager_TargetProcess* _Nonnull instance) __INTRODUCED_IN(36); + +/** + * Initializes an ADynamicInstrumentationManager_MethodDescriptor. Caller must clean up when they + * are done with ADynamicInstrumentationManager_MethodDescriptor_Destroy. + * + * @param fullyQualifiedClassName fqcn of class containing the method. + * @param methodName + * @param fullyQualifiedParameters fqcn of parameters of the method's signature, or e.g. "int" for + * primitives. + * @param numParameters length of `fullyQualifiedParameters` array. + */ +ADynamicInstrumentationManager_MethodDescriptor* _Nonnull + ADynamicInstrumentationManager_MethodDescriptor_create( + const char* _Nonnull fullyQualifiedClassName, const char* _Nonnull methodName, + const char* _Nonnull fullyQualifiedParameters[_Nonnull], size_t numParameters) + __INTRODUCED_IN(36); +/** + * Clean up an ADynamicInstrumentationManager_MethodDescriptor. + * + * @param instance returned from ADynamicInstrumentationManager_MethodDescriptor_create. + */ +void ADynamicInstrumentationManager_MethodDescriptor_destroy( + ADynamicInstrumentationManager_MethodDescriptor* _Nonnull instance) __INTRODUCED_IN(36); + +/** + * Get the containerPath calculated by + * ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @return The OS path of the containing file. + */ +const char* _Nullable ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance) + __INTRODUCED_IN(36); +/** + * Get the containerOffset calculated by + * ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @return The offset of the containing file within the process' memory. + */ +uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance) + __INTRODUCED_IN(36); +/** + * Get the methodOffset calculated by ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @return The offset of the method within the containing file. + */ +uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance) + __INTRODUCED_IN(36); +/** + * Clean up an ADynamicInstrumentationManager_ExecutableMethodFileOffsets. + * + * @param instance returned from ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + */ +void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance) + __INTRODUCED_IN(36); +/** + * Provides ART metadata about the described java method within the target process. + * + * @param targetProcess describes for which process the data is requested. + * @param methodDescriptor describes the targeted method. + * @param out will be populated with the data if successful. A nullptr combined + * with an OK status means that the program method is defined, but the offset + * info was unavailable because it is not AOT compiled. + * @return status indicating success or failure. The values correspond to the `binder_exception_t` + * enum values from <android/binder_status.h>. + */ +int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets( + const ADynamicInstrumentationManager_TargetProcess* _Nonnull targetProcess, + const ADynamicInstrumentationManager_MethodDescriptor* _Nonnull methodDescriptor, + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull* _Nullable out) + __INTRODUCED_IN(36); + +__END_DECLS + +#endif // __ADYNAMICINSTRUMENTATIONMANAGER_H__ diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index b025cb880ee7..a0460572abfc 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -4,6 +4,15 @@ LIBANDROID { AActivityManager_removeUidImportanceListener; # systemapi introduced=31 AActivityManager_isUidActive; # systemapi introduced=31 AActivityManager_getUidImportance; # systemapi introduced=31 + ADynamicInstrumentationManager_TargetProcess_create; # systemapi + ADynamicInstrumentationManager_TargetProcess_destroy; # systemapi + ADynamicInstrumentationManager_MethodDescriptor_create; # systemapi + ADynamicInstrumentationManager_MethodDescriptor_destroy; # systemapi + ADynamicInstrumentationManager_getExecutableMethodFileOffsets; # systemapi + ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath; # systemapi + ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset; # systemapi + ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset; # systemapi + ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy; # systemapi AAssetDir_close; AAssetDir_getNextFileName; AAssetDir_rewind; diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 2c8c261fa8f8..bacdec100890 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -957,6 +957,9 @@ <!-- Permission required for CTS test - CtsTelephonyTestCases --> <uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" /> + <!-- Permission required for ExecutableMethodFileOffsetsTest --> + <uses-permission android:name="android.permission.DYNAMIC_INSTRUMENTATION" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/services/core/Android.bp b/services/core/Android.bp index 6cfd44bb2d1a..b4d58806b5d8 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -158,6 +158,7 @@ java_library_static { "android.hardware.gnss-V2-java", "android.hardware.vibrator-V3-java", "app-compat-annotations", + "art_exported_aconfig_flags_lib", "framework-tethering.stubs.module_lib", "keepanno-annotations", "service-art.stubs.system_server", diff --git a/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java new file mode 100644 index 000000000000..8ec716077f46 --- /dev/null +++ b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 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 com.android.server.os.instrumentation; + +import static android.Manifest.permission.DYNAMIC_INSTRUMENTATION; +import static android.content.Context.DYNAMIC_INSTRUMENTATION_SERVICE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.PermissionManuallyEnforced; +import android.content.Context; +import android.os.instrumentation.ExecutableMethodFileOffsets; +import android.os.instrumentation.IDynamicInstrumentationManager; +import android.os.instrumentation.MethodDescriptor; +import android.os.instrumentation.TargetProcess; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.SystemService; + +import dalvik.system.VMDebug; + +import java.lang.reflect.Method; + +/** + * System private implementation of the {@link IDynamicInstrumentationManager interface}. + */ +public class DynamicInstrumentationManagerService extends SystemService { + public DynamicInstrumentationManagerService(@NonNull Context context) { + super(context); + } + + @Override + public void onStart() { + publishBinderService(DYNAMIC_INSTRUMENTATION_SERVICE, new BinderService()); + } + + private final class BinderService extends IDynamicInstrumentationManager.Stub { + @Override + @PermissionManuallyEnforced + public @Nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets( + @NonNull TargetProcess targetProcess, @NonNull MethodDescriptor methodDescriptor) { + if (!com.android.art.flags.Flags.executableMethodFileOffsets()) { + throw new UnsupportedOperationException(); + } + getContext().enforceCallingOrSelfPermission( + DYNAMIC_INSTRUMENTATION, "Caller must have DYNAMIC_INSTRUMENTATION permission"); + + if (targetProcess.processName == null + || !targetProcess.processName.equals("system_server")) { + throw new UnsupportedOperationException( + "system_server is the only supported target process"); + } + + Method method = parseMethodDescriptor( + getClass().getClassLoader(), methodDescriptor); + VMDebug.ExecutableMethodFileOffsets location = + VMDebug.getExecutableMethodFileOffsets(method); + + if (location == null) { + return null; + } + + ExecutableMethodFileOffsets ret = new ExecutableMethodFileOffsets(); + ret.containerPath = location.getContainerPath(); + ret.containerOffset = location.getContainerOffset(); + ret.methodOffset = location.getMethodOffset(); + return ret; + } + } + + @VisibleForTesting + static Method parseMethodDescriptor(ClassLoader classLoader, + @NonNull MethodDescriptor descriptor) { + try { + Class<?> javaClass = classLoader.loadClass(descriptor.fullyQualifiedClassName); + Class<?>[] parameters = new Class[descriptor.fullyQualifiedParameters.length]; + for (int i = 0; i < descriptor.fullyQualifiedParameters.length; i++) { + String typeName = descriptor.fullyQualifiedParameters[i]; + boolean isArrayType = typeName.endsWith("[]"); + if (isArrayType) { + typeName = typeName.substring(0, typeName.length() - 2); + } + switch (typeName) { + case "boolean": + parameters[i] = isArrayType ? boolean.class.arrayType() : boolean.class; + break; + case "byte": + parameters[i] = isArrayType ? byte.class.arrayType() : byte.class; + break; + case "char": + parameters[i] = isArrayType ? char.class.arrayType() : char.class; + break; + case "short": + parameters[i] = isArrayType ? short.class.arrayType() : short.class; + break; + case "int": + parameters[i] = isArrayType ? int.class.arrayType() : int.class; + break; + case "long": + parameters[i] = isArrayType ? long.class.arrayType() : long.class; + break; + case "float": + parameters[i] = isArrayType ? float.class.arrayType() : float.class; + break; + case "double": + parameters[i] = isArrayType ? double.class.arrayType() : double.class; + break; + default: + parameters[i] = isArrayType ? classLoader.loadClass(typeName).arrayType() + : classLoader.loadClass(typeName); + } + } + + return javaClass.getDeclaredMethod(descriptor.methodName, parameters); + } catch (ClassNotFoundException | NoSuchMethodException e) { + throw new IllegalArgumentException( + "The specified method cannot be found. Is this descriptor valid? " + + descriptor, e); + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3805c02d1bb9..221b8481a30c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -205,6 +205,7 @@ import com.android.server.os.BugreportManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; import com.android.server.os.NativeTombstoneManagerService; import com.android.server.os.SchedulingPolicyService; +import com.android.server.os.instrumentation.DynamicInstrumentationManagerService; import com.android.server.pdb.PersistentDataBlockService; import com.android.server.people.PeopleService; import com.android.server.permission.access.AccessCheckingService; @@ -2890,6 +2891,13 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(TracingServiceProxy.class); t.traceEnd(); + // UprobeStats DynamicInstrumentationManager + if (com.android.art.flags.Flags.executableMethodFileOffsets()) { + t.traceBegin("StartDynamicInstrumentationManager"); + mSystemServiceManager.startService(DynamicInstrumentationManagerService.class); + t.traceEnd(); + } + // It is now time to start up the app processes... t.traceBegin("MakeLockSettingsServiceReady"); diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp new file mode 100644 index 000000000000..2c2e5fdb68d9 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp @@ -0,0 +1,44 @@ +// Copyright (C) 2024 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], + default_team: "trendy_team_system_performance", +} + +android_test { + name: "DynamicInstrumentationManagerServiceTests", + srcs: ["src/**/*.java"], + + static_libs: [ + "androidx.test.core", + "androidx.test.runner", + "hamcrest-library", + "platform-test-annotations", + "services.core", + "testables", + "truth", + ], + + certificate: "platform", + platform_apis: true, + test_suites: [ + "device-tests", + ], +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml b/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml new file mode 100644 index 000000000000..4913d706a72d --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.os.instrumentation" > + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.os.instrumentation" + android:label="DynamicInstrumentationmanagerService Unit Tests"/> +</manifest>
\ No newline at end of file diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING b/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING new file mode 100644 index 000000000000..33defed0eae6 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "DynamicInstrumentationManagerServiceTests" + } + ] +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java new file mode 100644 index 000000000000..04073fab2059 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 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 com.android.server; + +public abstract class TestAbstractClass { + abstract void abstractMethod(); + + void concreteMethod() { + } +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java new file mode 100644 index 000000000000..2c25e7a52f73 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 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 com.android.server; + +public class TestAbstractClassImpl extends TestAbstractClass { + @Override + void abstractMethod() { + } +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java new file mode 100644 index 000000000000..085f5953f0e5 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 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 com.android.server; + +public class TestClass { + void primitiveParams(boolean a, boolean[] b, byte c, byte[] d, char e, char[] f, short g, + short[] h, int i, int[] j, long k, long[] l, float m, float[] n, double o, double[] p) { + } + + void classParams(String a, String[] b) { + } + + private void privateMethod() { + } + + /** + * docs! + */ + public void publicMethod() { + } + + private static class InnerClass { + private void innerMethod(InnerClass arg, InnerClass[] argArray) { + } + } +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java new file mode 100644 index 000000000000..7af4f254ab1c --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 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 com.android.server; + +/** + * docs! + */ +public interface TestInterface { + /** + * docs! + */ + void interfaceMethod(); + + /** + * docs! + */ + default void defaultMethod() { + } +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java new file mode 100644 index 000000000000..53aecbc08939 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 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 com.android.server; + +public class TestInterfaceImpl implements TestInterface { + @Override + public void interfaceMethod() { + } +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java new file mode 100644 index 000000000000..5492ba6b9dd1 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2024 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 com.android.server.os.instrumentation; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; + +import android.os.instrumentation.MethodDescriptor; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.TestAbstractClass; +import com.android.server.TestAbstractClassImpl; +import com.android.server.TestClass; +import com.android.server.TestInterface; +import com.android.server.TestInterfaceImpl; + +import org.junit.Test; + +import java.lang.reflect.Method; + + +/** + * Test class for + * {@link DynamicInstrumentationManagerService#parseMethodDescriptor(ClassLoader, + * MethodDescriptor)}. + * <p> + * Build/Install/Run: + * atest FrameworksMockingServicesTests:ParseMethodDescriptorTest + */ +@Presubmit +@SmallTest +public class ParseMethodDescriptorTest { + private static final String[] PRIMITIVE_PARAMS = new String[]{ + "boolean", "boolean[]", "byte", "byte[]", "char", "char[]", "short", "short[]", "int", + "int[]", "long", "long[]", "float", "float[]", "double", "double[]"}; + private static final String[] CLASS_PARAMS = + new String[]{"java.lang.String", "java.lang.String[]"}; + + @Test + public void primitiveParams() { + assertNotNull(parseMethodDescriptor(TestClass.class.getName(), "primitiveParams", + PRIMITIVE_PARAMS)); + } + + @Test + public void classParams() { + assertNotNull( + parseMethodDescriptor(TestClass.class.getName(), "classParams", CLASS_PARAMS)); + } + + @Test + public void publicMethod() { + assertNotNull( + parseMethodDescriptor(TestClass.class.getName(), "publicMethod")); + } + + @Test + public void privateMethod() { + assertNotNull( + parseMethodDescriptor(TestClass.class.getName(), "privateMethod")); + } + + @Test + public void innerClass() { + assertNotNull( + parseMethodDescriptor(TestClass.class.getName() + "$InnerClass", "innerMethod", + new String[]{TestClass.class.getName() + "$InnerClass", + TestClass.class.getName() + "$InnerClass[]"})); + } + + @Test + public void interface_concreteMethod() { + assertNotNull( + parseMethodDescriptor(TestInterfaceImpl.class.getName(), "interfaceMethod")); + } + + @Test + public void interface_defaultMethod() { + assertNotNull( + parseMethodDescriptor(TestInterface.class.getName(), "defaultMethod")); + } + + @Test + public void abstractClassImpl_abstractMethod() { + assertNotNull( + parseMethodDescriptor(TestAbstractClassImpl.class.getName(), "abstractMethod")); + } + + @Test + public void abstractClass_concreteMethod() { + assertNotNull( + parseMethodDescriptor(TestAbstractClass.class.getName(), "concreteMethod")); + } + + @Test + public void notFound_illegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> parseMethodDescriptor("foo", "bar")); + assertThrows(IllegalArgumentException.class, + () -> parseMethodDescriptor(TestClass.class.getName(), "bar")); + assertThrows(IllegalArgumentException.class, + () -> parseMethodDescriptor(TestClass.class.getName(), "primitiveParams", + new String[]{"int"})); + } + + private Method parseMethodDescriptor(String fqcn, String methodName) { + return DynamicInstrumentationManagerService.parseMethodDescriptor( + getClass().getClassLoader(), + getMethodDescriptor(fqcn, methodName, new String[]{})); + } + + private Method parseMethodDescriptor(String fqcn, String methodName, String[] fqParameters) { + return DynamicInstrumentationManagerService.parseMethodDescriptor( + getClass().getClassLoader(), + getMethodDescriptor(fqcn, methodName, fqParameters)); + } + + private MethodDescriptor getMethodDescriptor(String fqcn, String methodName, + String[] fqParameters) { + MethodDescriptor methodDescriptor = new MethodDescriptor(); + methodDescriptor.fullyQualifiedClassName = fqcn; + methodDescriptor.methodName = methodName; + methodDescriptor.fullyQualifiedParameters = fqParameters; + return methodDescriptor; + } + + +} |