ChildProcessLauncher.java revision 5f1c94371a64b3196d4be9466099bb892df9b88e
1// Copyright 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.content.browser; 6 7import android.content.Context; 8import android.graphics.SurfaceTexture; 9import android.util.Log; 10import android.util.Pair; 11import android.view.Surface; 12 13import com.google.common.annotations.VisibleForTesting; 14 15import org.chromium.base.CalledByNative; 16import org.chromium.base.JNINamespace; 17import org.chromium.base.ThreadUtils; 18import org.chromium.base.TraceEvent; 19import org.chromium.base.library_loader.Linker; 20import org.chromium.content.app.ChildProcessService; 21import org.chromium.content.app.ChromiumLinkerParams; 22import org.chromium.content.app.PrivilegedProcessService; 23import org.chromium.content.app.SandboxedProcessService; 24import org.chromium.content.common.IChildProcessCallback; 25import org.chromium.content.common.SurfaceWrapper; 26 27import java.util.ArrayList; 28import java.util.Map; 29import java.util.concurrent.ConcurrentHashMap; 30 31/** 32 * This class provides the method to start/stop ChildProcess called by native. 33 */ 34@JNINamespace("content") 35public class ChildProcessLauncher { 36 private static final String TAG = "ChildProcessLauncher"; 37 38 static final int CALLBACK_FOR_UNKNOWN_PROCESS = 0; 39 static final int CALLBACK_FOR_GPU_PROCESS = 1; 40 static final int CALLBACK_FOR_RENDERER_PROCESS = 2; 41 42 private static final String SWITCH_PROCESS_TYPE = "type"; 43 private static final String SWITCH_PPAPI_BROKER_PROCESS = "ppapi-broker"; 44 private static final String SWITCH_RENDERER_PROCESS = "renderer"; 45 private static final String SWITCH_GPU_PROCESS = "gpu-process"; 46 47 // The upper limit on the number of simultaneous sandboxed and privileged child service process 48 // instances supported. Each limit must not exceed total number of SandboxedProcessServiceX 49 // classes and PrivilegedProcessServiceX classes declared in this package and defined as 50 // services in the embedding application's manifest file. 51 // (See {@link ChildProcessService} for more details on defining the services.) 52 /* package */ static final int MAX_REGISTERED_SANDBOXED_SERVICES = 20; 53 /* package */ static final int MAX_REGISTERED_PRIVILEGED_SERVICES = 3; 54 55 private static class ChildConnectionAllocator { 56 // Connections to services. Indices of the array correspond to the service numbers. 57 private final ChildProcessConnection[] mChildProcessConnections; 58 59 // The list of free (not bound) service indices. When looking for a free service, the first 60 // index in that list should be used. When a service is unbound, its index is added to the 61 // end of the list. This is so that we avoid immediately reusing the freed service (see 62 // http://crbug.com/164069): the framework might keep a service process alive when it's been 63 // unbound for a short time. If a new connection to the same service is bound at that point, 64 // the process is reused and bad things happen (mostly static variables are set when we 65 // don't expect them to). 66 // SHOULD BE ACCESSED WITH mConnectionLock. 67 private final ArrayList<Integer> mFreeConnectionIndices; 68 private final Object mConnectionLock = new Object(); 69 70 private Class<? extends ChildProcessService> mChildClass; 71 private final boolean mInSandbox; 72 73 public ChildConnectionAllocator(boolean inSandbox) { 74 int numChildServices = inSandbox ? 75 MAX_REGISTERED_SANDBOXED_SERVICES : MAX_REGISTERED_PRIVILEGED_SERVICES; 76 mChildProcessConnections = new ChildProcessConnectionImpl[numChildServices]; 77 mFreeConnectionIndices = new ArrayList<Integer>(numChildServices); 78 for (int i = 0; i < numChildServices; i++) { 79 mFreeConnectionIndices.add(i); 80 } 81 setServiceClass(inSandbox ? 82 SandboxedProcessService.class : PrivilegedProcessService.class); 83 mInSandbox = inSandbox; 84 } 85 86 public void setServiceClass(Class<? extends ChildProcessService> childClass) { 87 mChildClass = childClass; 88 } 89 90 public ChildProcessConnection allocate( 91 Context context, ChildProcessConnection.DeathCallback deathCallback, 92 ChromiumLinkerParams chromiumLinkerParams) { 93 synchronized (mConnectionLock) { 94 if (mFreeConnectionIndices.isEmpty()) { 95 Log.e(TAG, "Ran out of services to allocate."); 96 assert false; 97 return null; 98 } 99 int slot = mFreeConnectionIndices.remove(0); 100 assert mChildProcessConnections[slot] == null; 101 mChildProcessConnections[slot] = new ChildProcessConnectionImpl(context, slot, 102 mInSandbox, deathCallback, mChildClass, chromiumLinkerParams); 103 return mChildProcessConnections[slot]; 104 } 105 } 106 107 public void free(ChildProcessConnection connection) { 108 synchronized (mConnectionLock) { 109 int slot = connection.getServiceNumber(); 110 if (mChildProcessConnections[slot] != connection) { 111 int occupier = mChildProcessConnections[slot] == null ? 112 -1 : mChildProcessConnections[slot].getServiceNumber(); 113 Log.e(TAG, "Unable to find connection to free in slot: " + slot + 114 " already occupied by service: " + occupier); 115 assert false; 116 } else { 117 mChildProcessConnections[slot] = null; 118 assert !mFreeConnectionIndices.contains(slot); 119 mFreeConnectionIndices.add(slot); 120 } 121 } 122 } 123 124 /** @return the count of connections managed by the allocator */ 125 @VisibleForTesting 126 int allocatedConnectionsCountForTesting() { 127 return mChildProcessConnections.length - mFreeConnectionIndices.size(); 128 } 129 } 130 131 // Service class for child process. As the default value it uses SandboxedProcessService0 and 132 // PrivilegedProcessService0. 133 private static final ChildConnectionAllocator sSandboxedChildConnectionAllocator = 134 new ChildConnectionAllocator(true); 135 private static final ChildConnectionAllocator sPrivilegedChildConnectionAllocator = 136 new ChildConnectionAllocator(false); 137 138 private static boolean sConnectionAllocated = false; 139 140 /** 141 * Sets service class for sandboxed service and privileged service. 142 */ 143 public static void setChildProcessClass( 144 Class<? extends SandboxedProcessService> sandboxedServiceClass, 145 Class<? extends PrivilegedProcessService> privilegedServiceClass) { 146 // We should guarantee this is called before allocating connection. 147 assert !sConnectionAllocated; 148 sSandboxedChildConnectionAllocator.setServiceClass(sandboxedServiceClass); 149 sPrivilegedChildConnectionAllocator.setServiceClass(privilegedServiceClass); 150 } 151 152 private static ChildConnectionAllocator getConnectionAllocator(boolean inSandbox) { 153 return inSandbox ? 154 sSandboxedChildConnectionAllocator : sPrivilegedChildConnectionAllocator; 155 } 156 157 private static ChildProcessConnection allocateConnection(Context context, 158 boolean inSandbox, ChromiumLinkerParams chromiumLinkerParams) { 159 ChildProcessConnection.DeathCallback deathCallback = 160 new ChildProcessConnection.DeathCallback() { 161 @Override 162 public void onChildProcessDied(ChildProcessConnection connection) { 163 if (connection.getPid() != 0) { 164 stop(connection.getPid()); 165 } else { 166 freeConnection(connection); 167 } 168 } 169 }; 170 sConnectionAllocated = true; 171 return getConnectionAllocator(inSandbox).allocate(context, deathCallback, 172 chromiumLinkerParams); 173 } 174 175 private static boolean sLinkerInitialized = false; 176 private static long sLinkerLoadAddress = 0; 177 178 private static ChromiumLinkerParams getLinkerParamsForNewConnection() { 179 if (!sLinkerInitialized) { 180 if (Linker.isUsed()) { 181 sLinkerLoadAddress = Linker.getBaseLoadAddress(); 182 if (sLinkerLoadAddress == 0) { 183 Log.i(TAG, "Shared RELRO support disabled!"); 184 } 185 } 186 sLinkerInitialized = true; 187 } 188 189 if (sLinkerLoadAddress == 0) 190 return null; 191 192 // Always wait for the shared RELROs in service processes. 193 final boolean waitForSharedRelros = true; 194 return new ChromiumLinkerParams(sLinkerLoadAddress, 195 waitForSharedRelros, 196 Linker.getTestRunnerClassName()); 197 } 198 199 private static ChildProcessConnection allocateBoundConnection(Context context, 200 String[] commandLine, boolean inSandbox) { 201 ChromiumLinkerParams chromiumLinkerParams = getLinkerParamsForNewConnection(); 202 ChildProcessConnection connection = 203 allocateConnection(context, inSandbox, chromiumLinkerParams); 204 if (connection != null) { 205 connection.start(commandLine); 206 } 207 return connection; 208 } 209 210 private static void freeConnection(ChildProcessConnection connection) { 211 getConnectionAllocator(connection.isInSandbox()).free(connection); 212 } 213 214 // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle. 215 private static final int NULL_PROCESS_HANDLE = 0; 216 217 // Map from pid to ChildService connection. 218 private static Map<Integer, ChildProcessConnection> sServiceMap = 219 new ConcurrentHashMap<Integer, ChildProcessConnection>(); 220 221 // A pre-allocated and pre-bound connection ready for connection setup, or null. 222 private static ChildProcessConnection sSpareSandboxedConnection = null; 223 224 // Manages oom bindings used to bind chind services. 225 private static BindingManager sBindingManager = BindingManagerImpl.createBindingManager(); 226 227 // Map from surface id to Surface. 228 private static Map<Integer, Surface> sViewSurfaceMap = 229 new ConcurrentHashMap<Integer, Surface>(); 230 231 // Map from surface texture id to Surface. 232 private static Map<Pair<Integer, Integer>, Surface> sSurfaceTextureSurfaceMap = 233 new ConcurrentHashMap<Pair<Integer, Integer>, Surface>(); 234 235 @VisibleForTesting 236 public static void setBindingManagerForTesting(BindingManager manager) { 237 sBindingManager = manager; 238 } 239 240 /** @return true iff the child process is protected from out-of-memory killing */ 241 @CalledByNative 242 private static boolean isOomProtected(int pid) { 243 return sBindingManager.isOomProtected(pid); 244 } 245 246 @CalledByNative 247 private static void registerViewSurface(int surfaceId, Surface surface) { 248 sViewSurfaceMap.put(surfaceId, surface); 249 } 250 251 @CalledByNative 252 private static void unregisterViewSurface(int surfaceId) { 253 sViewSurfaceMap.remove(surfaceId); 254 } 255 256 @CalledByNative 257 private static void registerSurfaceTexture( 258 int surfaceTextureId, int childProcessId, SurfaceTexture surfaceTexture) { 259 Pair<Integer, Integer> key = new Pair<Integer, Integer>(surfaceTextureId, childProcessId); 260 sSurfaceTextureSurfaceMap.put(key, new Surface(surfaceTexture)); 261 } 262 263 @CalledByNative 264 private static void unregisterSurfaceTexture(int surfaceTextureId, int childProcessId) { 265 Pair<Integer, Integer> key = new Pair<Integer, Integer>(surfaceTextureId, childProcessId); 266 sSurfaceTextureSurfaceMap.remove(key); 267 } 268 269 /** 270 * Sets the visibility of the child process when it changes or when it is determined for the 271 * first time. 272 */ 273 @CalledByNative 274 public static void setInForeground(int pid, boolean inForeground) { 275 sBindingManager.setInForeground(pid, inForeground); 276 } 277 278 /** 279 * Called when the embedding application is sent to background. 280 */ 281 public static void onSentToBackground() { 282 sBindingManager.onSentToBackground(); 283 } 284 285 /** 286 * Called when the embedding application is brought to foreground. 287 */ 288 public static void onBroughtToForeground() { 289 sBindingManager.onBroughtToForeground(); 290 } 291 292 /** 293 * Should be called early in startup so the work needed to spawn the child process can be done 294 * in parallel to other startup work. Must not be called on the UI thread. Spare connection is 295 * created in sandboxed child process. 296 * @param context the application context used for the connection. 297 */ 298 public static void warmUp(Context context) { 299 synchronized (ChildProcessLauncher.class) { 300 assert !ThreadUtils.runningOnUiThread(); 301 if (sSpareSandboxedConnection == null) { 302 sSpareSandboxedConnection = allocateBoundConnection(context, null, true); 303 } 304 } 305 } 306 307 private static String getSwitchValue(final String[] commandLine, String switchKey) { 308 if (commandLine == null || switchKey == null) { 309 return null; 310 } 311 // This format should be matched with the one defined in command_line.h. 312 final String switchKeyPrefix = "--" + switchKey + "="; 313 for (String command : commandLine) { 314 if (command != null && command.startsWith(switchKeyPrefix)) { 315 return command.substring(switchKeyPrefix.length()); 316 } 317 } 318 return null; 319 } 320 321 /** 322 * Spawns and connects to a child process. May be called on any thread. It will not block, but 323 * will instead callback to {@link #nativeOnChildProcessStarted} when the connection is 324 * established. Note this callback will not necessarily be from the same thread (currently it 325 * always comes from the main thread). 326 * 327 * @param context Context used to obtain the application context. 328 * @param commandLine The child process command line argv. 329 * @param fileIds The ID that should be used when mapping files in the created process. 330 * @param fileFds The file descriptors that should be mapped in the created process. 331 * @param fileAutoClose Whether the file descriptors should be closed once they were passed to 332 * the created process. 333 * @param clientContext Arbitrary parameter used by the client to distinguish this connection. 334 */ 335 @CalledByNative 336 static void start( 337 Context context, 338 final String[] commandLine, 339 int childProcessId, 340 int[] fileIds, 341 int[] fileFds, 342 boolean[] fileAutoClose, 343 long clientContext) { 344 TraceEvent.begin(); 345 assert fileIds.length == fileFds.length && fileFds.length == fileAutoClose.length; 346 FileDescriptorInfo[] filesToBeMapped = new FileDescriptorInfo[fileFds.length]; 347 for (int i = 0; i < fileFds.length; i++) { 348 filesToBeMapped[i] = 349 new FileDescriptorInfo(fileIds[i], fileFds[i], fileAutoClose[i]); 350 } 351 assert clientContext != 0; 352 353 int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS; 354 boolean inSandbox = true; 355 String processType = getSwitchValue(commandLine, SWITCH_PROCESS_TYPE); 356 if (SWITCH_RENDERER_PROCESS.equals(processType)) { 357 callbackType = CALLBACK_FOR_RENDERER_PROCESS; 358 } else if (SWITCH_GPU_PROCESS.equals(processType)) { 359 callbackType = CALLBACK_FOR_GPU_PROCESS; 360 } else if (SWITCH_PPAPI_BROKER_PROCESS.equals(processType)) { 361 inSandbox = false; 362 } 363 364 ChildProcessConnection allocatedConnection = null; 365 synchronized (ChildProcessLauncher.class) { 366 if (inSandbox) { 367 allocatedConnection = sSpareSandboxedConnection; 368 sSpareSandboxedConnection = null; 369 } 370 } 371 if (allocatedConnection == null) { 372 allocatedConnection = allocateBoundConnection(context, commandLine, inSandbox); 373 if (allocatedConnection == null) { 374 // Notify the native code so it can free the heap allocated callback. 375 nativeOnChildProcessStarted(clientContext, 0); 376 Log.e(TAG, "Allocation of new service failed."); 377 TraceEvent.end(); 378 return; 379 } 380 } 381 382 Log.d(TAG, "Setting up connection to process: slot=" + 383 allocatedConnection.getServiceNumber()); 384 triggerConnectionSetup(allocatedConnection, commandLine, childProcessId, filesToBeMapped, 385 callbackType, clientContext); 386 TraceEvent.end(); 387 } 388 389 @VisibleForTesting 390 static void triggerConnectionSetup( 391 final ChildProcessConnection connection, 392 String[] commandLine, 393 int childProcessId, 394 FileDescriptorInfo[] filesToBeMapped, 395 int callbackType, 396 final long clientContext) { 397 ChildProcessConnection.ConnectionCallback connectionCallback = 398 new ChildProcessConnection.ConnectionCallback() { 399 @Override 400 public void onConnected(int pid) { 401 Log.d(TAG, "on connect callback, pid=" + pid + " context=" + clientContext); 402 if (pid != NULL_PROCESS_HANDLE) { 403 sBindingManager.addNewConnection(pid, connection); 404 sServiceMap.put(pid, connection); 405 } 406 // If the connection fails and pid == 0, the Java-side cleanup was already 407 // handled by DeathCallback. We still have to call back to native for 408 // cleanup there. 409 if (clientContext != 0) { // Will be 0 in Java instrumentation tests. 410 nativeOnChildProcessStarted(clientContext, pid); 411 } 412 } 413 }; 414 415 // TODO(sievers): Revisit this as it doesn't correctly handle the utility process 416 // assert callbackType != CALLBACK_FOR_UNKNOWN_PROCESS; 417 418 connection.setupConnection(commandLine, 419 filesToBeMapped, 420 createCallback(childProcessId, callbackType), 421 connectionCallback, 422 Linker.getSharedRelros()); 423 } 424 425 /** 426 * Terminates a child process. This may be called from any thread. 427 * 428 * @param pid The pid (process handle) of the service connection obtained from {@link #start}. 429 */ 430 @CalledByNative 431 static void stop(int pid) { 432 Log.d(TAG, "stopping child connection: pid=" + pid); 433 ChildProcessConnection connection = sServiceMap.remove(pid); 434 if (connection == null) { 435 logPidWarning(pid, "Tried to stop non-existent connection"); 436 return; 437 } 438 sBindingManager.clearConnection(pid); 439 connection.stop(); 440 freeConnection(connection); 441 } 442 443 /** 444 * This implementation is used to receive callbacks from the remote service. 445 */ 446 private static IChildProcessCallback createCallback( 447 final int childProcessId, final int callbackType) { 448 return new IChildProcessCallback.Stub() { 449 /** 450 * This is called by the remote service regularly to tell us about new values. Note that 451 * IPC calls are dispatched through a thread pool running in each process, so the code 452 * executing here will NOT be running in our main thread -- so, to update the UI, we 453 * need to use a Handler. 454 */ 455 @Override 456 public void establishSurfacePeer( 457 int pid, Surface surface, int primaryID, int secondaryID) { 458 // Do not allow a malicious renderer to connect to a producer. This is only used 459 // from stream textures managed by the GPU process. 460 if (callbackType != CALLBACK_FOR_GPU_PROCESS) { 461 Log.e(TAG, "Illegal callback for non-GPU process."); 462 return; 463 } 464 465 nativeEstablishSurfacePeer(pid, surface, primaryID, secondaryID); 466 } 467 468 @Override 469 public SurfaceWrapper getViewSurface(int surfaceId) { 470 // Do not allow a malicious renderer to get to our view surface. 471 if (callbackType != CALLBACK_FOR_GPU_PROCESS) { 472 Log.e(TAG, "Illegal callback for non-GPU process."); 473 return null; 474 } 475 476 Surface surface = sViewSurfaceMap.get(surfaceId); 477 if (surface == null) { 478 Log.e(TAG, "Invalid surfaceId."); 479 return null; 480 } 481 assert surface.isValid(); 482 return new SurfaceWrapper(surface); 483 } 484 485 @Override 486 public SurfaceWrapper getSurfaceTextureSurface(int primaryId, int secondaryId) { 487 if (callbackType != CALLBACK_FOR_RENDERER_PROCESS) { 488 Log.e(TAG, "Illegal callback for non-renderer process."); 489 return null; 490 } 491 492 if (secondaryId != childProcessId) { 493 Log.e(TAG, "Illegal secondaryId for renderer process."); 494 return null; 495 } 496 497 Pair<Integer, Integer> key = new Pair<Integer, Integer>(primaryId, secondaryId); 498 // Note: This removes the surface and passes the ownership to the caller. 499 Surface surface = sSurfaceTextureSurfaceMap.remove(key); 500 if (surface == null) { 501 Log.e(TAG, "Invalid Id for surface texture."); 502 return null; 503 } 504 assert surface.isValid(); 505 return new SurfaceWrapper(surface); 506 } 507 }; 508 } 509 510 static void logPidWarning(int pid, String message) { 511 // This class is effectively a no-op in single process mode, so don't log warnings there. 512 if (pid > 0 && !nativeIsSingleProcess()) { 513 Log.w(TAG, message + ", pid=" + pid); 514 } 515 } 516 517 @VisibleForTesting 518 static ChildProcessConnection allocateBoundConnectionForTesting(Context context) { 519 return allocateBoundConnection(context, null, true); 520 } 521 522 /** @return the count of sandboxed connections managed by the allocator */ 523 @VisibleForTesting 524 static int allocatedConnectionsCountForTesting() { 525 return sSandboxedChildConnectionAllocator.allocatedConnectionsCountForTesting(); 526 } 527 528 /** @return the count of services set up and working */ 529 @VisibleForTesting 530 static int connectedServicesCountForTesting() { 531 return sServiceMap.size(); 532 } 533 534 private static native void nativeOnChildProcessStarted(long clientContext, int pid); 535 private static native void nativeEstablishSurfacePeer( 536 int pid, Surface surface, int primaryID, int secondaryID); 537 private static native boolean nativeIsSingleProcess(); 538} 539