1 /* 2 * Copyright (C) 2023 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 17 package com.android.server.art; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Build; 22 import android.os.RemoteException; 23 24 import androidx.annotation.RequiresApi; 25 26 import dalvik.system.VMRuntime; 27 28 import java.io.IOException; 29 30 /** 31 * JNI methods for ART Service with wrappers. 32 * 33 * The wrappers are added for two reasons: 34 * - They make the methods mockable, since Mockito cannot mock JNI methods. 35 * - They delegate calls to artd if the code is running for Pre-reboot Dexopt, to avoid loading 36 * libartservice.so. 37 * 38 * @hide 39 */ 40 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 41 public class ArtJni { 42 static { 43 // During Pre-reboot Dexopt, the code is loaded by a separate class loader from the chroot 44 // dir, where the new ART apex is mounted. In this case, loading libartservice.so is tricky. 45 // The library depends on libc++.so, libbase.so, etc. Although the classloader allows 46 // specifying a library search path, it doesn’t allow specifying how to search for 47 // dependencies. Because the classloading takes place in system server, the old linkerconfig 48 // takes effect rather than the new one, and the old linkerconfig doesn’t specify how to 49 // search for dependencies for the new libartservice.so. This leads to an undesired 50 // behavior: the dependencies are resolved to those on the old platform. 51 // 52 // Also, we can't statically link libartservice.so against all dependencies because it not 53 // only bloats libartservice.so by a lot, but also prevents us from accessing the global 54 // runtime instance when the code is running in the normal situation. 55 // 56 // Therefore, for Pre-reboot Dexopt, we just avoid loading libartservice.so, and delegate 57 // calls to artd instead. 58 if (!GlobalInjector.getInstance().isPreReboot()) { 59 if (VMRuntime.getRuntime().vmLibrary().equals("libartd.so")) { 60 System.loadLibrary("artserviced"); 61 } else { 62 System.loadLibrary("artservice"); 63 } 64 } 65 } 66 ArtJni()67 private ArtJni() {} 68 69 /** 70 * Returns an error message if the given dex path is invalid, or null if the validation passes. 71 */ 72 @Nullable validateDexPath(@onNull String dexPath)73 public static String validateDexPath(@NonNull String dexPath) { 74 if (GlobalInjector.getInstance().isPreReboot()) { 75 try { 76 return ArtdRefCache.getInstance().getArtd().validateDexPath(dexPath); 77 } catch (RemoteException e) { 78 Utils.logArtdException(e); 79 return null; 80 } 81 } 82 return validateDexPathNative(dexPath); 83 } 84 85 /** 86 * Returns an error message if the given class loader context is invalid, or null if the 87 * validation passes. 88 */ 89 @Nullable validateClassLoaderContext( @onNull String dexPath, @NonNull String classLoaderContext)90 public static String validateClassLoaderContext( 91 @NonNull String dexPath, @NonNull String classLoaderContext) { 92 if (GlobalInjector.getInstance().isPreReboot()) { 93 try { 94 return ArtdRefCache.getInstance().getArtd().validateClassLoaderContext( 95 dexPath, classLoaderContext); 96 } catch (RemoteException e) { 97 Utils.logArtdException(e); 98 return null; 99 } 100 } 101 return validateClassLoaderContextNative(dexPath, classLoaderContext); 102 } 103 104 /** 105 * Returns the name of the Garbage Collector currently in use in the Android Runtime. 106 */ 107 @NonNull getGarbageCollector()108 public static String getGarbageCollector() { 109 if (GlobalInjector.getInstance().isPreReboot()) { 110 // We don't need this for Pre-reboot Dexopt and we can't have this in artd because it 111 // needs access to the global runtime instance. 112 throw new UnsupportedOperationException(); 113 } 114 return getGarbageCollectorNative(); 115 } 116 117 /** 118 * Sets the system property {@code key} to {@code value}. 119 * 120 * @throws IllegalStateException if the operation fails. This caller should not expect this, 121 * unless the inputs are invalid (e.g., value too long). 122 */ setProperty(@onNull String key, @NonNull String value)123 public static Void setProperty(@NonNull String key, @NonNull String value) { 124 if (GlobalInjector.getInstance().isPreReboot()) { 125 // We don't need this for Pre-reboot Dexopt. 126 throw new UnsupportedOperationException(); 127 } 128 setPropertyNative(key, value); 129 // Return a placeholder value to make this method easier to mock. There is no good way to 130 // mock a method that is both void and static, due to the poor design of Mockito API. 131 return null; 132 } 133 134 /** 135 * Waits for processes whose executable is in the given directory to exit, and kills them if 136 * they don't exit within the timeout. 137 * 138 * Note that this method only checks processes' executable paths, not their open files. If the 139 * executable of a process is outside of the given directory but the process opens a file in 140 * that directory, this method doesn't handle it. 141 * 142 * After killing, the method waits another round with the given timeout. Theoretically, this 143 * method can take at most {@code 2 * timeoutMs}. However, the second round should be pretty 144 * fast in practice. 145 * 146 * This method assumes that no new process is started from an executable in the given directory 147 * while the method is running. It is the callers responsibility to make sure that this 148 * assumption holds. 149 * 150 * @throws IllegalArgumentException if {@code timeoutMs} is negative 151 * @throws IOException if the operation fails 152 */ ensureNoProcessInDir(@onNull String dir, int timeoutMs)153 public static Void ensureNoProcessInDir(@NonNull String dir, int timeoutMs) throws IOException { 154 if (GlobalInjector.getInstance().isPreReboot()) { 155 // We don't need this for Pre-reboot Dexopt. 156 throw new UnsupportedOperationException(); 157 } 158 ensureNoProcessInDirNative(dir, timeoutMs); 159 // Return a placeholder value to make this method easier to mock. There is no good way to 160 // mock a method that is both void and static, due to the poor design of Mockito API. 161 return null; 162 } 163 validateDexPathNative(@onNull String dexPath)164 @Nullable private static native String validateDexPathNative(@NonNull String dexPath); 165 @Nullable validateClassLoaderContextNative( @onNull String dexPath, @NonNull String classLoaderContext)166 private static native String validateClassLoaderContextNative( 167 @NonNull String dexPath, @NonNull String classLoaderContext); getGarbageCollectorNative()168 @NonNull private static native String getGarbageCollectorNative(); setPropertyNative(@onNull String key, @NonNull String value)169 private static native void setPropertyNative(@NonNull String key, @NonNull String value); ensureNoProcessInDirNative(@onNull String dir, int timeoutMs)170 private static native void ensureNoProcessInDirNative(@NonNull String dir, int timeoutMs) 171 throws IOException; 172 } 173